2011-03-24 14 views
9

Mis C encabezados por lo general se asemejan a la siguiente estilo para evitar múltiples inclusión:Buena C de estilo de cabecera

#ifndef <FILENAME>_H 
#define <FILENAME>_H 

// define public data structures/prototypes, macros etc. 

#endif /* !<FILENAME>_H */ 

Sin embargo, en su Notes on Programming in C, Rob Pike hace que el siguiente argumento acerca de los archivos de cabecera:

Hay un pequeño baile que involucra #ifdef que puede evitar que un archivo se lea dos veces, pero por lo general se hace incorrecto en la práctica; los #ifdef están en el archivo en sí, no en el archivo que lo incluye. El resultado es a menudo miles de líneas de código innecesarias que pasan por el analizador léxico, que es (en buenos compiladores) la fase más cara.

Por un lado, Pike es el único programador que realmente admiro. Por otro lado, poner varios #ifdef en múltiples archivos fuente en lugar de poner un #ifdef en un único archivo de encabezado se siente innecesariamente incómodo.

¿Cuál es la mejor manera de manejar el problema de la inclusión múltiple?

Respuesta

11

En mi opinión, utilice el método que requiere menos tiempo (lo que probablemente signifique poner los #ifdefs en los archivos de encabezado). Realmente no me importa si el compilador tiene que trabajar más si mi código resultante es más limpio. Si, tal vez, está trabajando en una base de código de múltiples millones de líneas que constantemente tiene que reconstruir por completo, tal vez valga la pena el ahorro adicional. Pero en la mayoría de los casos, sospecho que el costo adicional no suele ser notable.

+2

Encuentro esta respuesta muy útil. Lo curioso es que estoy de acuerdo de una manera más irónica: si la computadora puede hacer mi trabajo, ¿por qué lo haré? ;) –

6

Sigue haciendo lo que haces - Es claro, menos propenso a errores, y bien conocido por los escritores de compiladores, por lo que no es tan ineficiente como lo fue hace una década o dos.

Puede usar el no estándar #pragma once - Si busca, probablemente haya al menos una estantería que incluya guardias vs pragma una vez que se discuta, así que no voy a recomendar uno sobre el otro.

+0

Si no gcc gcc, detectaría el idioma estándar la primera vez que se incluye el encabezado, recuerde el nombre de archivo e ignore todas las solicitudes futuras para incluir ese nombre de archivo. Esto es puramente un caso de pereza del compilador. –

+1

@R. Todavía no? Pensé que todos los compiladores importantes tendrían esto hace una década. Por otra parte, leer un archivo dos veces no es realmente el recurso que tenía en el 89 cuando Pike escribió esas notas. – Erik

+1

@R ..: dice hacerlo - http://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html. Sin embargo, existen algunas restricciones que se utilizan para garantizar que la optimización sea válida, y podría haber falsos negativos en los casos en que las restricciones se rompan, pero, no obstante, la optimización sería válida en ese caso. –

2

La forma en que lo está haciendo actualmente es la manera común. El método de Pike reduce un poco el tiempo de compilación, pero con los compiladores modernos probablemente no mucho (cuando Pike escribió sus notas, los compiladores no estaban ligados al optimizador), los módulos están desordenados y son propensos a errores.

Aún podría cortar la inclusión múltiple al no incluir los encabezados de los encabezados, sino que debe documentarlos con "incluir <foodefs.h> antes de incluir este encabezado".

1

Te recomiendo que los pongas en el archivo fuente. No es necesario quejarse acerca de miles de líneas de código analizadas innecesarias con PC reales.

Además, es mucho más trabajo y fuente si comprueba cada encabezado en cada archivo fuente que incluye el encabezado.

Y tendría que manejar sus archivos de encabezado diferentes de los predeterminados y otros de terceros.

1

Pudo haber tenido una discusión la vez que estaba escribiendo esto. Hoy en día, los compiladores decentes son lo suficientemente inteligentes como para manejar esto bien.

+0

¿Referencias? Me parece que no son lo suficientemente inteligentes ... –

+1

@R., Hm, referencia, no, yo no tengo. Lo leí hace no mucho tiempo aquí en SO, que incluye guardias tratadas por gcc equivalentes a '#pragma una vez'. Y por cierto, el análisis léxico de '# ifdef' para ver primero qué es lo que un archivo debe analizarse en tiempo real no debería ser demasiado difícil, ¿no? En cualquier caso, nunca encontré para mí que * esto * fue un cuello de botella al compilar código grande. A menudo realizo una precompilación con ''E' para ver qué produce la fase de preprocesamiento y el ojo humano no puede medirla. –

0

Estoy de acuerdo con su enfoque, como otros han comentado, es más claro, autodocumentado y de menor mantenimiento.

Mi teoría sobre por qué Rob Pike podría haber sugerido su enfoque: está hablando de C, no de C++.

En C++, si tiene muchas clases y declara cada una en su propio archivo de encabezado, tendrá muchos archivos de encabezado. C realmente no proporciona este tipo de estructura fina (no recuerdo haber visto muchos archivos de encabezado C de una sola estructura), y los pares de archivos .h/.c tienden a ser más grandes y contienen algo así como un módulo o un subsistema. Por lo tanto, menos archivos de encabezado. En ese escenario, el enfoque de Rob Pike podría funcionar. Pero no lo veo como adecuado para programas C++ no triviales.

3

Pike escribió un poco más sobre ella en https://talks.golang.org/2012/splash.article:

En 1984, una compilación de ps.c, la fuente hasta el comando ps Unix, se observó a #include <sys/stat.h> 37 veces por el tiempo todo el preprocesamiento tenía ha hecho. Aunque los contenidos se descartan 36 veces, la mayoría de las implementaciones C abrirán el archivo, leerán y lo analizarán 37 veces. Sin gran astucia, de hecho, ese comportamiento es requerido por la macro semántica potencialmente compleja del preprocesador C .

Los compiladores se han vuelto bastante inteligentes desde: https://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html, por lo que esto no es un problema ahora.

La construcción de un solo binario en C++ en Google puede abrir y leer cientos de archivos de encabezado individuales decenas de miles de veces. En 2007, los ingenieros de construcción en Google instrumentaron la compilación de un gran binario de Google . El archivo contenía aproximadamente dos mil archivos que, si simplemente se concatenan juntos, sumaban 4,2 megabytes. Para el momento los #includes se habían expandido, se habían entregado más de 8 gigabytes a la entrada del compilador, una ampliación de 2000 bytes para cada byte fuente C++ .

Como otro punto de datos, en 2003 el sistema de compilación de Google se movió de un único Makefile a un diseño por directorio con dependencias explícitas mejor administradas, más . Un binario típico se redujo aproximadamente un 40% en tamaño de archivo, solo por tener grabadas dependencias más precisas. Aun así, las propiedades de C++ (o C para el caso) hacen que sea impráctico verificar esas dependencias automáticamente, y hoy todavía no tenemos una comprensión precisa de los requisitos de dependencia de los grandes binarios de Google C++ .

El punto acerca de los tamaños binarios sigue siendo relevante. Los compiladores (vinculadores) son bastante conservadores con respecto a la eliminación de símbolos no utilizados. How to remove unused C/C++ symbols with GCC and ld?

En Plan 9, los archivos de cabecera se les prohibió que contiene además #include cláusulas; todos los #includes estaban obligados a estar en el archivo C de nivel superior. Esto requirió cierta disciplina, por supuesto, el programador era requerido para enumerar las dependencias necesarias exactamente una vez, en el orden correcto , pero la documentación ayudó y en la práctica funcionó muy bien .

Esta es una posible solución.Otra posibilidad es tener una herramienta que administre los incluye para usted, por ejemplo MakeDeps.

También hay compilaciones unitarias, a veces denominadas unidades de compilación única (SCU). Hay herramientas para ayudar a administrar eso, como https://github.com/sakra/cotire

Usar un sistema de compilación que optimice la velocidad de compilación incremental también puede ser una ventaja. Estoy hablando de Bazel de Google y similares. Sin embargo, no lo protege de un cambio en un archivo de encabezado que se incluye en una gran cantidad de otros archivos.

Por último, hay una propuesta para los módulos de C++ en las obras, gran cosa https://groups.google.com/a/isocpp.org/forum/#!forum/modules. Consulte también What exactly are C++ modules?

+0

Como se ve en el primer párrafo de su segunda cita, aún en 2007, el problema sigue siendo importante. Si solo hay puntos de referencia más recientes disponibles. – FRIdSUN

+0

Según https://web.archive.org/web/20021218032155/http://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html, GCC podría hacer la optimización de inclusión múltiple ya a fines de 2002. Me pregunto qué compilador usaba Google en 2007. – user7610

Cuestiones relacionadas