Digamos que tenemos una base de código que se usa para muchos clientes diferentes, y tenemos un código que es relevante solo para clientes de tipo X. ¿Es mejor usar directivas de preprocesador para incluir este código solo en clientes de tipo X, o utilizar si las declaraciones? Para ser más claro:
// some code
#if TYPE_X_COSTUMER = 1
// do some things
#endif
// rest of the code
o
if(TYPE_X_COSTUMER) {
// do some things
}
Los argumentos en los que puedo pensar son:
- La directiva de preprocesador da como resultado una huella de código más pequeña y menos ramificaciones (en compiladores no optimizadores)
- Si las declaraciones dan como resultado un código que siempre se compila, por ejemplo, si alguien comete un error que dañará el código irrelevante para el proyecto en el que trabaja, el error seguirá apareciendo y no dañará la base del código. De lo contrario, no se dará cuenta de la corrupción.
- Siempre me dijeron que prefiera el uso del procesador sobre el uso del preprocesador (si esto es un argumento en absoluto ...)
¿Qué es preferible cuando se habla de una base de código para muchos clientes diferentes?
TYPE_X_CUSTOMER
siendo una macro de preprocesador?Respuestas:
Creo que hay una ventaja de usar un #define que no mencionó, y es el hecho de que puede establecer el valor en la línea de comando (por lo tanto, establecerlo desde su script de compilación de un solo paso).
Aparte de eso, generalmente es mejor evitar las macros. No respetan ningún alcance y eso puede causar problemas. Solo los compiladores muy tontos no pueden optimizar una condición basada en una constante de tiempo de compilación. No sé si eso es una preocupación para su producto (por ejemplo, podría ser importante mantener el código pequeño en las plataformas integradas).
fuente
En cuanto a muchas preguntas, la respuesta de esta pregunta es que depende . En lugar de decir cuál es mejor, he dado ejemplos y objetivos donde uno es mejor que otro.
Tanto el preprocesador como la constante tienen sus propios lugares de uso apropiado.
En el caso del preprocesador, el código se elimina antes del tiempo de compilación. Por lo tanto, es más adecuado para situaciones en las que se espera que el código no se compile . Esto puede afectar la estructura del módulo, las dependencias y puede permitir seleccionar los mejores segmentos de código para los aspectos de rendimiento. En los siguientes casos, uno debe dividir el código usando solo un preprocesador.
Código multiplataforma:
por ejemplo, cuando el código se compila en diferentes plataformas, cuando el código depende de números de versión específicos del sistema operativo (o incluso de la versión del compilador, aunque esto es muy raro). Por ejemplo, cuando se trata de contrapartes de código little-endien big-endien, deben segregarse con preprocesadores en lugar de constantes. O si está compilando código para Windows también Linux y ciertas llamadas al sistema son muy diferentes.
Parches experimentales:
Otro caso en el que esto es tener un justificado es algún código experimental que es arriesgado o ciertos módulos principales que deben omitirse, lo que tendrá un vínculo significativo o una diferencia de rendimiento. La razón por la que uno desearía deshabilitar el código a través del preprocesador en lugar de esconderse debajo de if () es porque es posible que no estemos seguros de los errores introducidos por este conjunto de cambios específico y que estemos ejecutándonos de manera experimental. Si falla, no debemos hacer nada más que deshabilitar ese código en producción que reescribir. En algún momento es ideal
#if 0
para comentar todo el código.Manejo de dependencias:
otra razón por la que es posible que desee generar Por ejemplo, si no desea admitir imágenes JPEG, puede ayudar a deshacerse de compilar ese módulo / código auxiliar y, finalmente, la biblioteca no se vinculará (estática o dinámicamente) a ese módulo. A veces, los paquetes se ejecutan
./configure
para identificar la disponibilidad de dicha dependencia y, si las bibliotecas no están presentes (o el usuario no quiere habilitar), dicha funcionalidad se deshabilita automáticamente sin vincularse con esa biblioteca. Aquí siempre es beneficioso si estas directivas se generan automáticamente.Licencias:
Un ejemplo muy interesante de directiva de preprocesador es ffmpeg . Tiene códecs que potencialmente pueden infringir patentes por su uso. Si descarga la fuente y compila para instalar, le pregunta si desea o no mantiene esas cosas. ¡Mantener los códigos ocultos bajo algunos si las condiciones aún pueden llevarte a la corte!
Código copiar y pegar:
macros Aka. Este no es un consejo para el uso excesivo de macros, solo que las macros tienen una forma mucho más poderosa de aplicar el equivalente de copiar pasado . Pero úsalo con mucho cuidado; y úsalo si sabes lo que estás haciendo. Constantes, por supuesto, no pueden hacer esto. Pero también se pueden usar
inline
funciones si eso es fácil de hacer.Entonces, ¿cuándo usas constantes?
Casi en todas partes.
Flujo de código más ordenado:
en general, cuando usa constantes, es casi indistinguible de las variables regulares y, por lo tanto, es un código mejor legible. Si escribe una rutina de 75 líneas, tenga 3 o 4 líneas cada 10 líneas con #ifdef es MUY incapaz de leer . Probablemente dada una constante primaria gobernada por #ifdef, y úsela en un flujo natural en todas partes.
Código bien sangrado: todas las directivas de preprocesador, nunca funcionan bien con un código bien sangrado . Incluso si su compilador permite la sangría de #def, el preprocesador Pre-ANSI C no permitió espacio entre el inicio de una línea y el carácter "#"; el "#" inicial siempre debe colocarse en la primera columna.
Configuración:
Otra razón por la cual las constantes o variables tienen sentido es que pueden evolucionar fácilmente de estar vinculadas a globales o en el futuro pueden extenderse para derivarse de los archivos de configuración.
Una última cosa:
nunca USE directivas de preprocesador
#ifdef
para#endif
cruzar el alcance o{ ... }
. es decir, inicio#ifdef
o fin de#endif
en diferentes lados de{ ... }
. Esto es extremadamente malo; puede ser confuso, a veces puede ser peligroso.Esto, por supuesto, no es una lista exhaustiva, pero muestra una gran diferencia, donde qué método es más apto para usar. Realmente no se trata de cuál es mejor , sino de cuál es más natural usar en un contexto dado.
fuente
¿Sus clientes tienen acceso a su código? Si es así, entonces el preprocesador podría ser una mejor opción. Tenemos algo similar y utilizamos indicadores de tiempo de compilación para varios clientes (o características específicas del cliente). Luego, un script podría extraer el código específico del cliente y enviamos ese código. Los clientes no tienen conocimiento de otros clientes u otras características específicas del cliente.
fuente
Algunos puntos adicionales dignos de mención:
El compilador puede procesar algunos tipos de expresiones constantes que el preprocesador no puede. Por ejemplo, el preprocesador generalmente no tendrá forma de evaluar
sizeof()
los tipos no primitivos. Esto puede forzar el uso deif()
en algunos casos.El compilador ignorará la mayoría de los problemas de sintaxis en el código que se omite
#if
(algunos problemas relacionados con el preprocesador aún pueden causar problemas) pero insistirá en la corrección sintáctica para el código omitidoif()
. Por lo tanto, si el código que se omite para algunas compilaciones pero no para otras se invalida como consecuencia de los cambios en otras partes del archivo (por ejemplo, identificadores renombrados), generalmente graznará en todas las compilaciones si está deshabilitadoif()
pero no si está deshabilitado por#if
. Dependiendo de por qué se omite el código, eso puede o no ser algo bueno.Algunos compiladores omitirán el código inalcanzable mientras que otros no; Sin embargo, las declaraciones de duración de almacenamiento estático dentro de un código inalcanzable, incluidos los literales de cadena, pueden asignar espacio incluso si ningún código puede usar el espacio así asignado.
Las macros pueden usar
if()
pruebas dentro de ellas, pero lamentablemente no existe un mecanismo para que el preprocesador realice ninguna lógica condicional dentro de una macro [tenga en cuenta que para usar dentro de las macros, el? :
operador a menudo puede ser mejorif
, pero se aplican los mismos principios].La
#if
directiva se puede usar para controlar qué macros se definen.Creo que a
if()
menudo es más limpio para la mayoría de los casos, excepto aquellos que implican tomar decisiones basadas en qué compilador está utilizando o qué macros deberían definirse;#if
Sin embargo, algunos de los problemas anteriores pueden requerir su uso , incluso en casos en losif
que parecería más limpio.fuente
Larga historia corta: los temores sobre las directivas de preprocesador son básicamente 2, inclusiones múltiples y tal vez un código menos legible y una lógica más críptica.
Si su proyecto es pequeño y casi no tiene un gran plan para el futuro, creo que ambas opciones ofrecen la misma relación entre pros y contras, pero si su proyecto va a ser enorme, considere algo más como escribir un archivo de encabezado separado si todavía desea usar directivas de preprocesador, pero tenga en cuenta que este tipo de enfoque generalmente hace que el código sea menos legible y asegúrese de que el nombre de esas constantes signifique algo para la lógica comercial del programa en sí.
fuente