Linux Fu: Generación automática de archivos de encabezado

He probado muchos de los lenguajes "más nuevos" y, de alguna manera, siempre soy más feliz cuando regreso a C ++ o incluso a C. Sin embargo, hay una cosa que me pone un poco nerviosa cuando regreso: la necesidad de tener encabezados con una declaración y luego un archivo separado con casi la misma información duplicada. Constantemente hago cambios y me olvido de actualizar el encabezado, y muchos otros idiomas se encargan de eso por ti. Entonces estaba buscando una forma de automatizar las cosas. Claro, algunos IDE insertarán automáticamente declaraciones, pero nunca me he sentido muy satisfecho con ellas por una variedad de razones. Quería algo ligero que pudiera usar en muchos equipos diferentes.

Sin embargo, encontré una herramienta más antigua que hace un buen trabajo, aunque tiene algunas limitaciones. La herramienta parece un poco confusa, así que pensé en mostrarte qué tipo de cabezas: parte del sistema de gestión del software de configuración Fossil. El programa data de 1993 cuando [Dwayne Richard Hipp] - el mismo tipo que escribió SQLite - lo creó para su propio uso. No es muy complejo: todo vive en un archivo fuente C bastante grande, pero puede escanear una carpeta y crear archivos de encabezado para todo. En algunos casos, no necesitará realizar cambios importantes en su código fuente, pero si lo desea, hay varias cosas que puede hacer.

El problema

Suponga que tiene dos archivos C que funcionan juntos. Digamos que tiene Ac y Bc Dentro del archivo A, tiene una función simple:

double ctof(double c)
{
  return (9.0*c)/f+32.0;
}

Si espera usar esto dentro del archivo B, debe haber una declaración para que cuando compile B, el compilador pueda saber que la función toma un solo argumento doble y devuelve un doble. Con ANSI C (y C ++) necesitas algo como:

double ctf(double c);

No hay programación real, solo una nota para el compilador sobre cómo se ve la función. Esto es lo que llamas un prototipo. Por lo general, creará un archivo de encabezado con el prototipo. Puede incluir ese encabezado tanto en Ac como en Bc

El problema es cuando cambia la función a Ac:

double ctof(double c1, int double c2)
{
  return (9.0*(c1+c2))/f+32.0;
}

Si no cambia el encabezado para que coincida, tendrá problemas. No solo eso, sino que tienes que hacer el mismo cambio. Si comete un error y marca los argumentos como flotantes en el encabezado, tampoco funcionará.

El programa

Suponiendo que haya instalado el software, simplemente puede ejecutarlo revisando todos los archivos C y H que desea que escanee. Por lo general, el globo *.[ch] hará el truco. También puede usarlo con archivos .cpp e incluso mezclar. De forma predeterminada, esto extraerá todas las declaraciones de variables globales y funciones globales que defina en una serie de archivos de encabezado.

¿Por qué una serie? El programa hace una suposición extraña que tiene sentido después de pensar en ello. Debido a que los encabezados se generan automáticamente, no tiene sentido reutilizar los encabezados. En cambio, cada archivo de origen obtiene su propio archivo de encabezado personalizado. El programa inserta lo que se necesita y en el orden correcto. Entonces Ac usará Ah y Bc usará Bh. No habrá interdependencia entre los dos encabezados. Si algo cambia, simplemente ejecute el programa nuevamente para regenerar los archivos de encabezado.

¿Qué se copia?

Esto es lo que dice la documentación que se copia en archivos de encabezado:

  • Cuando se define una función en cualquier archivo .c, se coloca un prototipo de esa función en el archivo .h generado de cada archivo .c que llama a la función. Si la palabra clave "estacionaria" de C aparece al principio de la definición de la función, el prototipo se suprime. Si usa el "LOCAL"Una palabra clave en la que normalmente diría"static”, Luego se genera un prototipo, pero solo aparecerá en el archivo de encabezado único que corresponde al archivo fuente que contiene la función. Sin embargo, ningún otro archivo de encabezado generado contendrá un prototipo para la función estática porque solo tiene un campo de archivo. Si llama a makeheaders con una opción de línea de comando "-local", entonces se dirige al "static"palabra clave como"LOCAL”Y genera prototipos en el archivo de cabecera que corresponde al archivo fuente que contiene la definición de la función.
  • Cuando se define una variable global en un archivo .c, "extern”Una declaración de esa variable se coloca en el encabezado de cada archivo .c que usa la variable.
    Cuando una estructura, unión o listado o prototipo de función o declaración de clase C ++ aparece en un archivo .h producido a mano, esa declaración se copia en los archivos .h generados automáticamente de todos los archivos .c que usan la estructura, unión, conteo , función o clase. Pero las declaraciones que aparecen en un archivo .c se consideran privadas para ese archivo .cy no se copian en ningún archivo generado automáticamente.
  • Todos los desafíos de #define y type que aparecen en archivos .h hechos a mano se copian en archivos .h generados automáticamente según sea necesario. Las construcciones similares que aparecen en los archivos .c se consideran privadas para esos archivos y no se copian. Cuando aparece una declaración de estructura, unión o listado en un archivo .h, makeheaders generará automáticamente un typedef, lo que hace posible hacer referencia a la declaración sin el "struct","union"o"enum"Calificatorio.
  • Tenga en cuenta que la herramienta puede saber cuándo un encabezado es el que produce, por lo que no tiene que excluirlos de los archivos de entrada.

    Ejemplo de C ++

    Para cosas como las clases de C ++, o cualquier cosa, en realidad, puede insertar un bloque de código en una directiva de preprocesador especial para que la herramienta lo procese. Aquí hay un ejemplo muy simple que usé para probar cosas:

    Algunas cosas a tener en cuenta. Primero, la inclusión de test.hpp capturará el archivo de encabezado generado específico para este archivo. La INTERFACE una directiva envuelve el código que debería estar en el encabezado. En tiempo de compilación, INTERFACE será igual a cero, por lo que este código no se compilará dos veces.

    Las funciones de membresía declaradas fuera del INTERFACE la sección tiene PUBLIC delante de ellos (y podría, por supuesto, tener PRIVATE o PROTECTED, además). Esto hará que la herramienta los recoja. Finalmente, observe que hay una variable global y una función global en la parte inferior del archivo.

    Note esto cuando use PUBLIC o las otras palabras clave, omite las funciones de la declaración. La única razón por la que el ejemplo tiene algunas funciones es porque están en línea. Si coloca todas las funciones fuera de la sección de interfaz del archivo, el encabezado generado ensamblará correctamente la declaración de clase. En este caso, agregará estas características a las que ya están allí.

    La cabeza generada

    El encabezado parece bastante normal. Es posible que se sorprenda de que el encabezado no esté empaquetado con las instrucciones habituales del preprocesador que impiden que el archivo se incluya más de una vez. Después de todo, debido a que solo un archivo incluirá el encabezado, ese código es innecesario.

    Aquí está el archivo:

    Darse cuenta de INTERFACE se establece en cero al final, lo que significa que en el archivo fuente, la parte de la interfaz no se volverá a compilar. Para una fuente C, la herramienta también genera desafíos de tipo para cosas como estructuras. Para C ++, esto es innecesario, por supuesto. Puede ver que el subproducto tiene algunas declaraciones en la sección de interfaz y algunas en la sección de implementación: hay una etiqueta pública redundante. Esto es seguro y no aparecería si coloco todo el código fuera de la sección de interfaz.

    Hay más

    Hay más de lo que puede hacer esta versátil herramienta, pero puede leer la documentación. Hay una bandera que descarta información sobre su código que puede utilizar con fines documentales. Puede crear jerarquías de interfaces. También puede ayudarlo a mezclar código C ++ y C. La herramienta es lo suficientemente inteligente como para manejar la compilación condicional. Sin embargo, tenga en cuenta que el soporte de C ++ no maneja cosas como plantillas y espacios de nombres. Sin embargo, tiene la fuente, por lo que podría solucionarlo si lo desea. Hay algunas otras limitaciones sobre las que debería leer antes de adoptar esto para un proyecto grande.

    ¿Probará una herramienta de este tipo o se contenta con manejar los encabezados a mano? C ++ puede incluso apuntar a páginas web. O utilícelo para scripts de shell si se atreve.

Maya Lorenzo
Maya Lorenzo

Deja una respuesta

Tu dirección de correo electrónico no será publicada.