Организация файлов C - PullRequest
       24

Организация файлов C

28 голосов
/ 07 сентября 2008

Я привык делать все свое кодирование в одном C-файле. Тем не менее, я работаю над проектом, достаточно большим, чтобы это стало непрактичным. Я включал их вместе, но сталкивался с случаями, когда несколько раз включал некоторые файлы и т. Д. Я слышал о файлах .h, но я не уверен, какова их функция (или почему 2 файла лучше, чем 1).

Какие стратегии я должен использовать для организации своего кода? Можно ли отделить «публичные» функции от «приватных» для конкретного файла?

Этот вопрос ускорил мой запрос. В файле tea.h нет ссылки на файл tea.c. Знает ли компилятор, что каждый файл .h имеет соответствующий файл .c?

Ответы [ 8 ]

35 голосов
/ 07 сентября 2008

Вы должны рассматривать файлы .h как файлы интерфейса вашего файла .c. Каждый файл .c представляет собой модуль с определенным количеством функций. Если функции в файле .c используются другими модулями (то есть другими файлами .c), поместите прототип функции в файл интерфейса .h. Включив файл интерфейса в исходный файл модулей .c и все остальные файлы .c, в которых требуется эта функция, вы сделаете эту функцию доступной для других модулей.

Если вам нужна только функция в определенном файле .c (не в любом другом модуле), объявите его область действия статическим. Это означает, что он может быть вызван только из файла c, в котором он определен.

То же самое касается переменных, которые используются в нескольких модулях. Они должны войти в заголовочный файл и там они должны быть помечены ключевым словом «extern». Примечание. Для функций ключевое слово extern необязательно. Функции всегда считаются «внешними».

Защита включения в заголовочных файлах помогает не включать один и тот же заголовочный файл несколько раз.

Например:

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;
    }

Module1.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();
    }
10 голосов
/ 07 сентября 2008

Постарайтесь, чтобы каждый .c сосредоточился на определенной области функциональности. Используйте соответствующий файл .h для объявления этих функций.

Каждый файл .h должен иметь защиту заголовка своего содержимого. Например:

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

Таким образом, вы можете включать «account.h» столько раз, сколько захотите, и первый раз, когда он будет отображаться в определенном модуле компиляции, будет единственным, который фактически извлечет его содержимое.

7 голосов
/ 07 сентября 2008

Компилятор

Вы можете увидеть пример модуля C на в этой теме - Обратите внимание, что есть два файла - заголовок tea.h и код tea.c. Вы объявляете все общедоступные определения, переменные и прототипы функций, к которым другие программы должны обращаться в заголовке. В вашем основном проекте вы #include и этот код теперь может получить доступ к функциям и переменным чайного модуля, которые упомянуты в заголовке.

После этого все становится немного сложнее. Если вы используете Visual Studio и многие другие IDE, которые управляют вашей сборкой, игнорируйте эту часть - они позаботятся о компиляции и связывании объектов.

Linker

Когда вы компилируете два отдельных файла C, компилятор создает отдельные объектные файлы - поэтому main.c становится main.o, а tea.c становится tea.o. Работа компоновщика состоит в том, чтобы просмотреть все объектные файлы (ваши main.o и tea.o) и сопоставить ссылки - поэтому, когда вы вызываете функцию чая в main, компоновщик изменяет этот вызов, так что он действительно вызывает правильный функция в чае. Компоновщик создает исполняемый файл.

Существует замечательное учебное пособие , в котором более подробно рассматривается эта тема, включая сферу и другие проблемы, с которыми вы столкнетесь.

Удачи!

-Adam

7 голосов
/ 07 сентября 2008

Несколько простых правил для начала:

  1. Поместите те объявления, которые вы хотите сделать "общедоступными", в заголовочный файл для создаваемого вами файла реализации C.
  2. Только заголовочные файлы #include в файле C, необходимые для реализации файла C.
  3. включать заголовочные файлы в заголовочный файл, только если требуется для объявлений в этом заголовочном файле.

  4. Используйте метод include guard, описанный Эндрю ИЛИ, используйте # pragma один раз , если его поддерживает компилятор (который делает то же самое - иногда более эффективно)
3 голосов
/ 07 сентября 2008

Помимо ответов, приведенных выше, одним небольшим преимуществом разделения вашего кода на модули (отдельные файлы) является то, что если вам нужны глобальные переменные, вы можете ограничить их область действия одним модулем с помощью ключевое слово «статический». (Вы также можете применить это к функциям). Обратите внимание, что это использование «static» отличается от его использования внутри функции.

3 голосов
/ 07 сентября 2008

Чтобы ответить на ваш дополнительный вопрос:

Это вопрос ускорил мой запрос. В файле tea.h нет ссылок на файл tea.c Компилятор "знает" что каждый файл .h имеет соответствующий .c файл?

Компилятор не касается в первую очередь заголовочных файлов. Каждый вызов компилятора компилирует исходный файл (.c) в файл объекта (.o). За кулисами (т. Е. В файле make или файле проекта) создается эквивалентная ему командная строка:

compiler --options tea.c

Исходный файл #include содержит все файлы заголовков для ресурсов, на которые он ссылается, - так компилятор находит файлы заголовков.

(здесь я примыкаю к ​​некоторым деталям. Есть много чего узнать о построении проектов на Си.)

1 голос
/ 07 сентября 2008

Ваш вопрос проясняет, что вы на самом деле не сделали много серьезных разработок. Обычный случай - ваш код будет слишком большим, чтобы поместиться в один файл. Хорошее правило заключается в том, что вы должны разбивать функциональность на логические единицы (файлы .c), и каждый файл должен содержать не более того, что вы можете легко удерживать в своей голове за один раз.

Данный программный продукт обычно включает выходные данные из множества различных файлов .c. Обычно это делается так, что компилятор создает несколько объектных файлов (в системах Unix ".o" файлы VC генерируют файлы .obj). Назначение «компоновщика» состоит в том, чтобы объединить эти объектные файлы в выходные данные (совместно используемую библиотеку или исполняемый файл).

Как правило, ваши файлы реализации (.c) содержат фактический исполняемый код, в то время как заголовочные файлы (.h) имеют объявления открытых функций в этих файлах реализации. Вы можете легко иметь больше заголовочных файлов, чем файлов реализации, и иногда заголовочные файлы также могут содержать встроенный код.

Обычно файлы реализации довольно часто включают друг друга. Хорошей практикой является обеспечение того, чтобы каждый файл реализации отделял свои проблемы от других файлов.

Я бы порекомендовал вам скачать и посмотреть исходный код ядра Linux. Это довольно масштабно для C-программы, но хорошо организовано в отдельные области функциональности.

0 голосов
/ 07 сентября 2008

.h файлы должны использоваться для определения прототипов для ваших функций. Это необходимо, чтобы вы могли включить нужные вам прототипы в ваш C-файл, не объявляя все нужные вам функции в одном файле.

Например, когда вы #include <stdio.h>, это предоставляет прототипы для printf и других функций ввода-вывода. Символы для этих функций обычно загружаются компилятором по умолчанию. Вы можете посмотреть системные файлы .h в / usr / include, если вас интересуют обычные идиомы, связанные с этими файлами.

Если вы пишете только тривиальные приложения с небольшим количеством функций, на самом деле нет необходимости модульно разбирать все на логические группы процедур. Однако, если у вас есть необходимость в разработке большой системы, вам необходимо уделить внимание тому, где определить каждую из ваших функций.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...