Pregunta

Estoy acostumbrado a hacer toda mi codificación en un archivo C.Sin embargo, estoy trabajando en un proyecto lo suficientemente grande como para que resulte poco práctico hacerlo.Los he #incluido juntos pero me he encontrado con casos en los que #incluido algunos archivos varias veces, etc.He oído hablar de los 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 debo utilizar para organizar mi código?¿Es posible separar las funciones "públicas" de las "privadas" para un archivo en particular?

Este La 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?

¿Fue útil?

Solución

Debes considerar los 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 de un archivo .c son utilizadas por otros módulos (es decir,otros archivos .c) colocan el prototipo de función en el archivo de interfaz .h.Al incluir el archivo de interfaz en el archivo .c de sus módulos originales y en cualquier otro archivo .c en el que necesite la función, hace que esta función esté disponible para 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 llamar desde el archivo c en el que está definido.

Lo mismo ocurre con las variables que se utilizan en varios módulos.Deben ir en el archivo de encabezado y allí deben marcarse con la palabra clave "externo".Nota:Para 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:

Módulo1.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

Módulo2.c

    #include "Module.1.h"

    static void MyLocalFunction(void);

    static void MyLocalFunction(void)
    {
      MyExternVariable = 1u;
      MyExternFunction();
    }

Otros consejos

Intente hacer que cada .c se centre en un área particular de funcionalidad.Utilice el archivo .h correspondiente para declarar esas funciones.

Cada archivo .h debe tener un protector de "encabezado" alrededor de su contenido.Por ejemplo:

#ifndef ACCOUNTS_H
#define ACCOUNTS_H
....
#endif

De esa manera puedes incluir "accounts.h" tantas veces como quieras, y la primera vez que se vea en una unidad de compilación en particular será la única que realmente extraiga su contenido.

Compilador

Puedes ver un ejemplo de un 'módulo' C en este tema - Tenga en cuenta que hay dos archivos: el encabezado tea.h y el código tea.c.Declaras todas las definiciones públicas, variables y prototipos de funciones a las que deseas que otros programas accedan en el encabezado.En tu proyecto principal #incluirás y ese código ahora puede acceder a las funciones y variables del módulo de té que se mencionan en el encabezado.

Después de eso, se vuelve un poco más complejo.Si está utilizando Visual Studio y muchos otros IDE que administran su compilación por usted, ignore esta parte: ellos se encargan de compilar y vincular objetos.

Enlazador

Cuando compila dos archivos C separados, el compilador produce archivos objeto individuales, por lo que main.c se convierte en main.o y tea.c se convierte en tea.o.El trabajo del vinculador es mirar todos los archivos objeto (su main.o y tea.o) y hacer coincidir las referencias, de modo que cuando llama a una función de té en principal, el vinculador modifica esa llamada para que realmente llame a la función correcta. Función en el té.El vinculador produce el archivo ejecutable.

Hay un gran tutorial que profundiza más sobre este tema, incluido el alcance y otros problemas con los que se encontrará.

¡Buena suerte!

-Adán

Un par de reglas simples para comenzar:

  1. Coloque aquellas declaraciones que desee hacer "públicas" en el archivo de encabezado del archivo de implementación C que está creando.
  2. Solo #include archivos de encabezado en el archivo C que son necesarios para implementar el archivo C.
  3. Incluir archivos de encabezado en un archivo de encabezado solo si es necesario para las declaraciones dentro de ese archivo de encabezado.

  4. Utilice el método de protección de inclusión descrito por Andrew O utilice #pragma una vez si el compilador lo admite (que hace lo mismo, a veces de manera más eficiente)

Para responder a su pregunta adicional:

EsteLa 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 fuente (.c) en un archivo objeto (.o).Detrás de escena (es decir,en el make archivo o archivo de proyecto) se está generando una línea de comando equivalente a esta:

compiler --options tea.c

El archivo fuente #includes todos los archivos de encabezado de los recursos a los que hace referencia, que es como el compilador encuentra los archivos de encabezado.

(Estoy pasando por alto algunos detalles aquí.Hay mucho que aprender sobre la construcción de proyectos C.)

Además de las respuestas proporcionadas anteriormente, una pequeña ventaja de dividir su código en módulos (archivos separados) es que si necesita 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 funciones).Tenga en cuenta que este uso de "estático" es diferente de su uso dentro de una función.

Su pregunta deja en claro que realmente no ha realizado mucho desarrollo serio.El caso habitual es que su código generalmente será demasiado grande para caber en un solo archivo.Una buena regla es dividir la funcionalidad en unidades lógicas (archivos .c) y cada archivo no debe contener más de lo que puede tener fácilmente en su cabeza al mismo tiempo.

Un producto de software determinado generalmente incluye la salida de muchos archivos .c diferentes.La forma en que normalmente se hace esto es que el compilador produce una cantidad de archivos objeto (en los archivos ".o" de los sistemas Unix, VC genera archivos .obj).El propósito del "enlazador" es componer estos archivos objeto en la salida (ya sea una biblioteca compartida o un ejecutable).

Generalmente, 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.Es muy fácil tener 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.

Generalmente 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 demás archivos.

Le recomendaría que descargue y mire la fuente del kernel de Linux.Es bastante masivo para un programa en C, pero está bien organizado en áreas separadas de funcionalidad.

Los archivos .h deben usarse para definir los prototipos de 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 usted #include <stdio.h>, esto proporciona los prototipos para printf y otras funciones IO.Los símbolos para estas funciones normalmente los carga el compilador de forma predeterminada.Puede consultar los archivos .h del sistema en /usr/include si está interesado en los modismos normales relacionados con estos archivos.

Si sólo está escribiendo aplicaciones triviales con pocas funciones, no es realmente necesario modularizar todo en agrupaciones lógicas de procedimientos.Sin embargo, si necesita desarrollar un sistema grande, deberá considerar dónde definir cada una de sus funciones.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top