lenguaje - Organización de archivos C
guardar y leer datos en un archivo.txt en c (8)
Intente hacer que cada foco .c se enfoque en un área particular de funcionalidad. Use el archivo .h correspondiente para declarar esas funciones.
Cada archivo .h debe tener un "encabezado" de protección alrededor de su contenido. Por ejemplo:
#ifndef ACCOUNTS_H
#define ACCOUNTS_H
....
#endif
De esta forma, puede incluir "accounts.h" tantas veces como desee, y la primera vez que se ve en una unidad de compilación en particular será la única que realmente atraiga su contenido.
Estoy acostumbrado a hacer toda mi codificación en un solo archivo C. Sin embargo, estoy trabajando en un proyecto lo suficientemente grande como para que no sea práctico hacerlo. He estado #including them together pero me he encontrado con casos donde estoy #incluyendo algunos archivos varias veces, etc. He oído hablar de archivos .h, pero no estoy seguro de cuál es su función (o por qué tener 2 archivos es mejor que 1).
¿Qué estrategias debería usar para organizar mi código? ¿Es posible separar las funciones "públicas" de las "privadas" para un archivo en particular?
Esta pregunta precipitó mi investigación. El archivo tea.h no hace referencia al archivo tea.c. ¿El compilador "sabe" que cada archivo .h tiene un archivo .c correspondiente?
Su pregunta deja en claro que realmente no ha hecho mucho desarrollo serio. El caso habitual es que su código generalmente será demasiado grande para caber en un archivo. Una buena regla es que debe dividir la funcionalidad en unidades lógicas (archivos .c) y cada archivo debe contener no más de lo que puede mantener fácilmente en su cabeza al mismo tiempo.
Entonces, un producto de software dado generalmente incluye el resultado de muchos archivos .c diferentes. Cómo se hace normalmente esto es que el compilador produce una cantidad de archivos de objeto (en los archivos de sistema ".o" de unix, VC genera archivos .obj). El propósito del "vinculador" es componer estos archivos de objeto en el resultado (ya sea una biblioteca compartida o un ejecutable).
En general, sus archivos de implementación (.c) contienen código ejecutable real, mientras que los archivos de encabezado (.h) tienen las declaraciones de las funciones públicas en esos archivos de implementación. Puede tener fácilmente más archivos de encabezado que archivos de implementación y, a veces, los archivos de encabezado también pueden contener código en línea.
En general, es bastante inusual que los archivos de implementación se incluyan entre sí. Una buena práctica es garantizar que cada archivo de implementación separe sus preocupaciones de los otros archivos.
Recomiendo que descargue y mire la fuente del kernel de Linux. Es bastante masivo para un programa C, pero está bien organizado en áreas separadas de funcionalidad.
Un par de reglas simples para comenzar:
- Coloque las declaraciones que desea hacer "públicas" en el archivo de encabezado para el archivo de implementación de C que está creando.
- Solo #incluye archivos de encabezado en el archivo C necesarios para implementar el archivo C.
incluir archivos de encabezado en un archivo de encabezado solo si es necesario para las declaraciones dentro de ese archivo de encabezado.
- Use el método de inclusión de protector descrito por Andrew O use #pragma una vez si el compilador lo admite (lo cual hace lo mismo, a veces de manera más eficiente)
Además de las respuestas proporcionadas anteriormente, una pequeña ventaja de entablillar su código en módulos (archivos separados) es que si tiene que tener variables globales, puede limitar su alcance a un solo módulo mediante el uso de la palabra clave '' estático''. (También puedes aplicar esto a las funciones). Tenga en cuenta que este uso de ''estático'' es diferente de su uso dentro de una función.
Los archivos .h se deben usar para definir los prototipos para sus funciones. Esto es necesario para que pueda incluir los prototipos que necesita en su archivo C sin declarar todas las funciones que necesita en un solo archivo.
Por ejemplo, cuando #include <stdio.h>
, esto proporciona los prototipos para printf y otras funciones de IO. Los símbolos para estas funciones normalmente son cargados por el compilador de forma predeterminada. Puede ver los archivos .h del sistema en / usr / include si le interesan las expresiones idiomáticas habituales relacionadas con estos archivos.
Si solo está escribiendo aplicaciones triviales con pocas funciones, no es realmente necesario modular todo en agrupaciones lógicas de procedimientos. Sin embargo, si tiene la necesidad de desarrollar un sistema grande, tendrá que prestar atención en cuanto a dónde definir cada una de sus funciones.
Debería considerar archivos .h como archivos de interfaz de su archivo .c. Cada archivo .c representa un módulo con una cierta cantidad de funcionalidad. Si las funciones en un archivo .c son utilizadas por otros módulos (es decir, otros archivos .c), coloque el prototipo de función en el archivo de interfaz .h. Al incluir el archivo de interfaz en el archivo .c de los módulos originales y en cualquier otro archivo .c en el que necesite la función, pone esta función a disposición de otros módulos.
Si solo necesita una función en un determinado archivo .c (no en ningún otro módulo), declare su alcance estático. Esto significa que solo se puede invocar desde el archivo c en el que está definido.
Lo mismo ocurre con las variables que se utilizan en múltiples módulos. Deben ir en el archivo de encabezado y allí tienen que marcarse con la palabra clave ''extern''. Nota: Para las funciones, la palabra clave ''extern'' es opcional. Las funciones siempre se consideran ''externas''.
Las protecciones de inclusión en los archivos de encabezado ayudan a no incluir el mismo archivo de encabezado varias veces.
Por ejemplo:
Module1.c:
#include "Module1.h" static void MyLocalFunction(void); static unsigned int MyLocalVariable; unsigned int MyExternVariable; void MyExternFunction(void) { MyLocalVariable = 1u; /* Do something */ MyLocalFunction(); } static void MyLocalFunction(void) { /* Do something */ MyExternVariable = 2u; }
Módulo1.h:
#ifndef __MODULE1.H #define __MODULE1.H extern unsigned int MyExternVariable; void MyExternFunction(void); #endif
Module2.c
#include "Module.1.h" static void MyLocalFunction(void); static void MyLocalFunction(void) { MyExternVariable = 1u; MyExternFunction(); }
Compilador
Puede ver un ejemplo de un ''módulo'' en este tema : tenga en cuenta que hay dos archivos: el encabezado tea.h y el código tea.c. Usted declara todas las definiciones públicas, las variables y los prototipos de función a los que desea que accedan otros programas en el encabezado. En su proyecto principal, #incluirá y ese código ahora puede acceder a las funciones y variables del módulo de té que se mencionan en el encabezado.
Se pone un poco más complejo después de eso. Si está usando Visual Studio y muchos otros IDEs que administran su compilación para usted, entonces ignore esta parte, ellos se encargan de compilar y vincular objetos.
Enlazador
Cuando compila dos archivos C separados, el compilador produce archivos de objetos individuales, por lo que main.c se convierte en main.o, y tea.c se convierte en tea.o. El trabajo del enlazador es mirar todos los archivos del objeto (su main.o y tea.o), y hacer coincidir las referencias, de modo que cuando llama a una función tea en main, el enlazador modifica esa llamada, por lo que llama al derecho función en el té. El enlazador produce el archivo ejecutable.
Hay un gran tutorial que profundiza en este tema, incluido el alcance y otros problemas que se encontrarán.
¡Buena suerte!
-Adán
Para responder a tu pregunta adicional:
Esta pregunta precipitó mi investigación. El archivo tea.h no hace referencia al archivo tea.c. ¿El compilador "sabe" que cada archivo .h tiene un archivo .c correspondiente?
El compilador no se preocupa principalmente por los archivos de encabezado. Cada invocación del compilador compila un archivo de origen (.c) en un archivo de objeto (.o). Detrás de escena (es decir, en el archivo make
o el archivo de proyecto) se está generando una línea de comando equivalente a esta:
compiler --options tea.c
El archivo fuente #include
s todos los archivos de encabezado para los recursos a los que hace referencia, que es cómo el compilador encuentra los archivos de encabezado.
(Estoy pasando por alto algunos detalles aquí. Hay mucho que aprender sobre la construcción de proyectos de C.)