sirve - ¿Por qué debería evitarse#ifdef en los archivos.c?
libreria ifdef (11)
Un objetivo razonable pero no tan bueno como una regla estricta.
El consejo para probar y mantener los condicionales del preprocesador en los archivos de cabecera es bueno, ya que le permite seleccionar interfaces de manera condicional pero no ensuciar el código con una lógica confusa y fea del preprocesador.
Sin embargo, hay muchos, muchos lotes de código que se parecen al ejemplo inventado a continuación, y no creo que haya una alternativa claramente mejor. Creo que usted ha citado una guía razonable pero no un gran mandamiento de una tableta de oro.
#if defined(SOME_IOCTL)
case SOME_IOCTL:
...
#endif
#if defined(SOME_OTHER_IOCTL)
case SOME_OTHER_IOCTL:
...
#endif
#if defined(YET_ANOTHER_IOCTL)
case YET_ANOTHER_IOCTL:
...
#endif
Un programador que respeto dijo que en el código C, #if
y #ifdef
deben evitarse a toda costa, excepto posiblemente en los archivos de cabecera. ¿Por qué se consideraría una mala práctica de programación usar #ifdef
en un archivo .c?
Porque entonces, cuando haces resultados de búsqueda, no sabes si el código está dentro o fuera sin leerlo.
Debido a que deben usarse para las dependencias del SO / Plataforma, y por lo tanto, ese tipo de código debe estar en archivos como io_win.c o io_macos.c
(Algo fuera de la pregunta)
Una vez vi un consejo que sugiere el uso de los #if(n)def/#endif
para usar en el código de depuración / aislamiento en lugar de comentar.
Se sugirió para ayudar a evitar situaciones en las que la sección a comentar ya tenía comentarios de documentación y se debería implementar una solución como la siguiente:
/* <-- begin debug cmnt if (condition) /* comment */
/* <-- restart debug cmnt {
....
}
*/ <-- end debug cmnt
En su lugar, esto sería:
#ifdef IS_DEBUGGED_SECTION_X
if (condition) /* comment */
{
....
}
#endif
Me pareció una buena idea. Ojalá pudiera recordar la fuente para poder vincularla :(
CPP es un lenguaje de macros separado (que no es de Turing-completo) encima de (generalmente) C o C ++. Como tal, es fácil confundirse entre este idioma y el idioma base, si no se tiene cuidado. Ese es el argumento habitual contra las macros en lugar de, por ejemplo, las plantillas c ++. Pero #ifdef? Simplemente intente leer el código de otra persona que nunca ha visto antes, que tiene un montón de ifdefs.
por ejemplo, intente leer estas funciones de Reed-Solomon multiply-a-block-by-constant-Galois-value: http://parchive.cvs.sourceforge.net/viewvc/parchive/par2-cmdline/reedsolomon.cpp?revision=1.3&view=markup
Si no tuvo la siguiente sugerencia, tardará un minuto en descubrir qué sucede: hay dos versiones: una simple y otra con una tabla de búsqueda precalculada (LONGMULTIPLY). Aun así, diviértete rastreando el #if BYTE_ORDER == __LITTLE_ENDIAN. Me resultó mucho más fácil de leer cuando reescribí ese bit para usar una función le16_to_cpu, (cuya definición estaba dentro de las cláusulas #if) inspirada en las cosas de byteorder.h de Linux.
Si necesita diferentes comportamientos de bajo nivel dependiendo de la compilación, intente encapsular eso en funciones de bajo nivel que proporcionen un comportamiento consistente en todas partes, en lugar de poner cosas #if directamente en sus funciones más grandes.
Difícil de mantener. Utilice mejor las interfaces para abstraer el código específico de la plataforma que abusar de la compilación condicional dispersando #ifdef
s en toda su implementación.
P.ej
void foo() {
#ifdef WIN32
// do Windows stuff
#else
// do Posix stuff
#endif
// do general stuff
}
No es agradable En su lugar, tenga los archivos foo_w32.c
y foo_psx.c
con
foo_w32.c:
void foo() {
// windows implementation
}
foo_psx.c:
void foo() {
// posix implementation
}
foo.h:
void foo(); // common interface
Luego tenga 2 makefiles 1 : Makefile.win
, Makefile.psx
, con cada uno compilando el archivo .c
apropiado y enlazando con el objeto correcto.
Enmienda menor:
Si la implementación de foo()
depende de algún código que aparece en todas las plataformas, por ejemplo, common_stuff()
2 , simplemente llame a eso en sus implementaciones de foo()
.
P.ej
common.h:
void common_stuff(); // May be implemented in common.c, or maybe has multiple
// implementations in common_{A, B, ...} for platforms
// { A, B, ... }. Irrelevant.
foo_ {w32, psx} .c:
void foo() { // Win32/Posix implementation
// Stuff
...
if (bar) {
common_stuff();
}
}
Si bien puede estar repitiendo una llamada de función a common_stuff()
, no puede parametrizar su definición de foo()
por plataforma a menos que siga un patrón muy específico. En general, las diferencias de plataforma requieren implementaciones completamente diferentes y no siguen dichos patrones.
- Los makefiles se utilizan aquí ilustrativamente. Es posible que su sistema de compilación no use
make
en absoluto, como si usa Visual Studio, CMake, Scons, etc. - Incluso si
common_stuff()
tiene varias implementaciones, que varían según la plataforma.
Es cierto que #if #endif complica la lectura del código. Sin embargo, he visto una gran cantidad de códigos del mundo real que no tienen problemas al usar esto y siguen siendo fuertes. Por lo tanto, puede haber mejores maneras de evitar el uso de #if #endif, pero usarlos no es tan malo si se toma el cuidado adecuado.
La compilación condicional es difícil de depurar. Uno tiene que conocer todos los ajustes para averiguar qué bloque de código ejecutará el programa.
Una vez pasé una semana depurando una aplicación de subprocesos múltiples que usaba compilación condicional. El problema era que el identificador no se escribía igual. Un módulo usó #if FEATURE_1
mientras que el área problemática usó #if FEATURE1
(Observe el subrayado).
Soy un gran defensor de dejar que el makefile
maneje la configuración incluyendo las bibliotecas u objetos correctos. Hace que el código sea más legible. Además, la mayoría del código se vuelve independiente de la configuración y solo unos pocos archivos dependen de la configuración.
Mi interpretación de esta regla: su lógica de programa (algorítmica) no debe estar influenciada por las definiciones del preprocesador. El funcionamiento de su código siempre debe ser conciso. Cualquier otra forma de lógica (plataforma, depuración) debe ser abstracta en los archivos de cabecera.
Esto es más una pauta que una regla estricta, en mi humilde opinión. Pero estoy de acuerdo en que las soluciones basadas en c-sintaxis son preferibles a la magia de preprocesador.
Por supuesto, favorecer la abstracción sobre la compilación condicional. Sin embargo, como cualquier persona que haya escrito software portátil puede decirle, la cantidad de permutaciones ambientales es asombrosa. Algunas disciplinas de diseño pueden ayudar, pero a veces la elección es entre la elegancia y el cumplimiento de un calendario. En tales casos, podría ser necesario un compromiso.
Respuesta tardía, pero me topé con esto mientras buscaba algo relacionado.
Considere la situación en la que debe proporcionar un código totalmente probado, con una cobertura de sucursales del 100%, etc. Ahora agregue la compilación condicional.
Cada símbolo único utilizado para controlar la compilación condicional duplica el número de variantes de código que necesita probar. Entonces, un símbolo, tienes dos variantes. Dos símbolos, ahora tiene cuatro formas diferentes de compilar su código. Y así.
Y esto solo aplica para pruebas booleanas como #ifdef
. Puede imaginar fácilmente el problema si una prueba tiene la forma #if VARIABLE == SCALAR_VALUE_FROM_A_RANGE
.
Si su código se compilará con diferentes compiladores de C y utiliza características específicas del compilador, entonces es posible que deba determinar qué macros predefinidas están disponibles.