¿Es mejor usar la directiva de preprocesador o la declaración if (constante)?

10

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?

MByD
fuente
3
¿Cuáles son las probabilidades de que envíe una compilación no creada utilizando un compilador de optimización? Y si sucede, ¿el impacto será importante (especialmente cuando se pone en contexto con todas las otras optimizaciones que perderá entonces)?
@delnan: el código se usa para muchas plataformas diferentes con muchos compiladores diferentes. Y con respecto al impacto, podría en ciertos casos.
MByD
¿Te dijeron que prefieras algo? Eso es como obligar a alguien a comer alimentos del KFC mientras no le gusta el pollo.
derecha el
@WTP: no, no lo estaba, estoy interesado en saber más, por lo que tomaría mejores decisiones en el futuro.
MByD
En su segundo ejemplo, ¿sigue TYPE_X_CUSTOMERsiendo una macro de preprocesador?
detly

Respuestas:

5

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).

Tamás Szelei
fuente
En realidad, esto es para sistemas embebidos
MByD
El problema es que los sistemas embebidos usualmente usan algún compilador antiguo (como por ejemplo gcc 2.95).
B 24овић
@VJo - GCC es el caso de la suerte :)
MByD
Pruébalo entonces. Si incluye el código no utilizado y ese es un tamaño considerable, entonces vaya por las definiciones.
Tamás Szelei
Si desea poder configurarlo a través de la línea de comando del compilador, todavía usaría una constante en el código y solo #definiría el valor asignado a esa constante.
CodesInChaos
12

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.

  1. 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.

  2. 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.

  3. 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 ./configurepara 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.

  4. 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!

  5. 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 inlinefunciones si eso es fácil de hacer.

Entonces, ¿cuándo usas constantes?
Casi en todas partes.

  1. 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.

  2. 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.

  3. 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 #ifdefpara #endif cruzar el alcance o { ... }. es decir, inicio #ifdefo fin de #endifen 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.

Dipan Mehta
fuente
Gracias por la larga y detallada respuesta. Estaba al tanto de la mayoría de las cosas que mencionó y se omitieron de la pregunta, ya que la pregunta no es si usar capacidades de preprocesador, sino un tipo específico de uso. Pero creo que el punto básico que hizo es cierto.
MByD
1
"Nunca USE las directivas de preprocesador #ifdef a #endif cruzando el alcance o {...}" depende del IDE. Si IDE reconoce bloques preprocesadores inactivos y los colapsa o muestra con un color diferente, no es confuso.
Abyx
@Abyx es cierto que IDE puede ayudar. Sin embargo, en muchos casos donde las personas se desarrollan bajo la plataforma * nix (linux, etc.), la elección de los editores, etc.son muchas (VI, emacs, etc.). Entonces, alguien más puede estar usando un editor diferente al suyo.
Dipan Mehta
4

¿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.

Aditya Sehgal
fuente
3

Algunos puntos adicionales dignos de mención:

  1. 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 de if()en algunos casos.

  2. 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 omitido if(). 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á deshabilitado if()pero no si está deshabilitado por #if. Dependiendo de por qué se omite el código, eso puede o no ser algo bueno.

  3. 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.

  4. 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 mejor if, pero se aplican los mismos principios].

  5. La #ifdirectiva 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; #ifSin embargo, algunos de los problemas anteriores pueden requerir su uso , incluso en casos en los ifque parecería más limpio.

Super gato
fuente
1

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í.

Micro
fuente
Esta es una gran base de código, y las definiciones están en algunos encabezados por separado.
MByD