Вопрос

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

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

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

Это было полезно?

Решение

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

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

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

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

Например:

Модуль1.с:

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

Модуль1.h:

    #ifndef __MODULE1.H
    #define __MODULE1.H

    extern unsigned int MyExternVariable;

    void MyExternFunction(void);      

    #endif

Модуль2.c

    #include "Module.1.h"

    static void MyLocalFunction(void);

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

Другие советы

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

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

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

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

Компилятор

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

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

Линкер

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

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

Удачи!

-Адам

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

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

  4. Используйте метод защиты включения, описанный Эндрю, ИЛИ используйте #прагма один раз если компилятор поддерживает это (что делает то же самое, иногда более эффективно)

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

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

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

compiler --options tea.c

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top