Структура проекта C - заголовок на модуль против одного большого заголовка - PullRequest
5 голосов
/ 04 июня 2009

В течение моей карьеры программиста я работал с рядом проектов на Си, и структуры заголовочных файлов обычно попадают в один из следующих двух шаблонов:

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

Преимущества варианта 2 очевидны для меня - это удешевляет разделение модуля между несколькими проектами и упрощает просмотр зависимостей между модулями.

Но каковы преимущества варианта 1? У него должны быть некоторые преимущества, иначе он не был бы таким популярным.


Этот вопрос применим как к C ++, так и к C, но я никогда не видел № 1 в проекте C ++.

Размещение #define с, struct с и т. Д. Также различается, но для этого вопроса я бы хотел сосредоточиться на прототипах функций.

Ответы [ 9 ]

8 голосов
/ 04 июня 2009

Я думаю, что главной мотивацией для # 1 является ... лень. Люди думают, что или слишком сложно управлять зависимостями, которые деление вещей на отдельные файлы могут сделать более очевидными, и / или считают, что это несколько «излишне» - иметь отдельные файлы для всего.

Конечно, это также часто может быть случай «исторических причин», когда программа или проект выросли из чего-то небольшого, и никто не потратил время на рефакторинг заголовочных файлов.

4 голосов
/ 07 августа 2010

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

4 голосов
/ 04 июня 2009

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

4 голосов
/ 04 июня 2009
  • 1 просто не нужно. Я не вижу веских причин для этого, и достаточно, чтобы этого избежать.

Три правила для следующего # 2 и без проблем:

  • начать КАЖДЫЙ заголовочный файл с

    #ifndef _HEADER_Namefile
    #define _HEADER_Namefile_
    

конец файла с

    #endif

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

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

    extern unsigned long G_BEER_COUNTER;
    

, который указывает компилятору, что символ G_BEER_COUNTER на самом деле является длинным без знака (то есть работает как объявление), что в каком-то другом модуле он будет иметь правильное определение / инициализацию. (Это также позволяет компоновщику сохранять таблицу разрешенных / неразрешенных символов.) Фактическое определение (то же выражение без extern) содержится в файле .c модуля.

  • только при доказанной абсолютной необходимости вы включаете другие заголовки в заголовочный файл. операторы include должны быть видны только в файлах .c (модулях). Это позволяет лучше интерпретировать зависимости и находить / решать проблемы.
3 голосов
/ 04 июня 2009

Существует также, я полагаю, третий вариант: каждый .c имеет свой собственный .h, но есть также один .h, который включает в себя все другие .h файлы. Это приносит лучшее из обоих миров за счет поддержания .h в актуальном состоянии, хотя это может быть сделано автоматически.

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

3 голосов
/ 04 июня 2009

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

1 голос
/ 04 июня 2009

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

0 голосов
/ 04 июня 2009

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

Например, если я хочу использовать векторы STL, я просто включаю их, и мне все равно, какие внутренние компоненты необходимы для использования вектора. GCC включает в себя 8 других заголовков - распределитель, algobase, конструкция, неинициализированный, вектор и bvector. Было бы больно включать все эти 8 только для использования вектора, вы согласны?

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

0 голосов
/ 04 июня 2009

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

Один заголовочный файл для всего проекта не работает, если проект не очень маленький - как школьное задание

...