Определения макросов для заголовков, куда их класть?

При определении макросов, на которые полагаются заголовки, например _FILE_OFFSET_BITS, FUSE_USE_VERSION, _GNU_SOURCE, где это лучшее место для размещения?

Некоторые возможности, которые я рассмотрел, включают

  • В верхней части любых исходных файлов, которые полагаются на определения, представленные заголовками, включенными в этот файл
  • Непосредственно перед включением для соответствующего заголовка (ов)
  • Определить на уровне CPPFLAGS через компилятор? (например, -D_FILE_OFFSET_BITS=64) для:
    • Целый репо-источник
    • Весь проект
    • Только те источники, которые этого требуют.
  • В заголовках проектов, которые также должны включать соответствующие заголовки, к которым применяются макросы.
  • Некоторое другое место, о котором я не думал, но бесконечно превосходен

Примечание. Основанием для моего решения является обоснование применимостью к make, autotools и другим системам сборки.

Ответы

Ответ 1

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

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

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

Ответ 2

Ну, это зависит.

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

Что касается _FILE_OFFSET_BITS, я бы действительно не определял его явно, а использовал getconf LFS_CFLAGS и getconf LFS_LDFLAGS.

Ответ 3

Я всегда ставил их в командной строке через CPPFLAGS для всего проекта. Если вы поместите их в другое место, существует опасность того, что вы можете забыть скопировать их в новый исходный файл или включить системный заголовок, прежде чем включать заголовок проекта, который их определяет, и это может привести к чрезвычайно неприятным ошибкам (например, объявление одного файла устаревший 32-разрядный struct stat и передающий свой адрес функции в другой файл, ожидающий 64-разрядный struct stat).

Кстати, действительно смешно, что _FILE_OFFSET_BITS=64 по-прежнему не является значением по умолчанию для glibc.

Ответ 4

Большинство проектов, которые я видел, использовали их с помощью параметров командной строки -D. Они есть, потому что это облегчает построение источника с различными компиляторами и системными заголовками. Если вы должны были построить системный компилятор для другой системы, которая им не нужна, или для них нужен другой набор, тогда configure script может легко изменить аргументы командной строки, которые файл make передает в компилятор.

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

Они, конечно, раздражают, чтобы не отставать.

Ответ 5

Для _GNU_SOURCE и, в частности, для автотюнов, вы можете использовать AC_USE_SYSTEM_EXTENSIONS (цитируя это отдельно от руководства autoconf):

- Макро: AC_USE_SYSTEM_EXTENSIONS
    Этот макрос был введен в Autoconf 2.60. Если возможно, включите     расширения до C или Posix на хостах, которые обычно отключают     расширения, как правило, из-за пространства имен стандартов соответствия     вопросы. Это нужно вызывать перед любыми макросами, которые запускают C     компилятор. Следующие макросы препроцессора определены где     соответственно:

_GNU_SOURCE         Включить расширения в GNU/Linux.

__EXTENSIONS__         Включить общие расширения в Solaris.

_POSIX_PTHREAD_SEMANTICS         Включить расширения потоков в Solaris.

_TANDEM_SOURCE         Включить расширения для платформы HP NonStop.

_ALL_SOURCE         Включить расширения для AIX 3 и для Interix.

_POSIX_SOURCE         Включить функции Posix для Minix.

_POSIX_1_SOURCE         Включить дополнительные функции Posix для Minix.

_MINIX         Определите платформу Minix. Этот конкретный макрос препроцессора          устаревает и может быть удалена в будущем выпуске          Autoconf.

Для _FILE_OFFSET_BITS вам нужно вызвать AC_SYS_LARGEFILE и AC_FUNC_FSEEKO:

- макрос: AC_SYS_LARGEFILE

Упорядочить для 64-битных смещений файлов, известных как поддержка большого файла. На некоторых хостах необходимо использовать специальные параметры компилятора для создания программ, которые могут обращаться к большим файлам. Добавьте такие параметры к выходной переменной CC. Определите _FILE_OFFSET_BITS и _LARGE_FILES при необходимости.

Поддержка больших файлов может быть отключена путем настройки с помощью опции --disable-largefile.

Если вы используете этот макрос, проверьте, работает ли ваша программа, даже если off_t больше, чем long int, так как это распространено, когда включена поддержка большого файла. Например, неверно печатать произвольное значение off_t X с printf("%ld", (long int) X).

В LFS были введены функции fseeko и ftello для замены их C-аналогов fseek и ftell, которые не используют off_t. Пользуйтесь AC_FUNC_FSEEKO, чтобы сделать их прототипы доступными при их использовании, а поддержка большого файла включена.

Если вы используете autoheader для генерации config.h, вы можете определить другие макросы, которые вам нравятся с помощью AC_DEFINE или AC_DEFINE_UNQUOTED:

AC_DEFINE([FUSE_VERSION], [28], [FUSE Version.])

Затем определение будет передано в командную строку или помещено в config.h, если вы используете autoheader. Реальное преимущество AC_DEFINE заключается в том, что он легко позволяет определять препроцессоры в результате проверок конфигурации и отделяет системный крейт от важных деталей.

При записи файла .c сначала #include "config.h" заголовок интерфейса (например, foo.h для foo.c) гарантирует, что заголовок не имеет отсутствующих зависимостей), а затем все остальные заголовки.

Ответ 6

Я обычно ставил их как можно ближе к тем вещам, которые им нужны, хотя вы не устанавливаете их неправильно.

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

void something (void) {
    // 600 lines of code here
    int x = fn(y);
    // more code here
}

намного лучше, чем:

void something (void) {
    int x;
    // 600 lines of code here
    x = fn(y);
    // more code here
}

так как вам не нужно искать тип x в последнем случае.


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

gcc -Dmydefine=7 -o binary7 source.c
gcc -Dmydefine=9 -o binary9 source.c

Однако, если каждая компиляция этого файла будет использовать 7, его можно перемещать ближе к месту, где оно использовалось:

source.c:
    #include <stdio.h>

    #define mydefine 7
    #include "header_that_uses_mydefine.h"

    #define mydefine 7
    #include "another_header_that_uses_mydefine.h"

Обратите внимание, что я сделал это дважды, чтобы он был более локализован. Это не проблема, поскольку, если вы меняете только один, компилятор расскажет вам об этом, но он гарантирует, что вы знаете, что эти параметры заданы для определенных заголовков.


И если вы уверены, что никогда не будете включать (например) bitio.h без первой настройки BITCOUNT до 8, вы даже можете зайти так далеко, чтобы создать файл bitio8.h, содержащий только:

#define BITCOUNT 8
#include "bitio.h"

а затем просто включите bitio8.h в исходные файлы.

Ответ 7

Использование заголовочных файлов - это то, что я рекомендую, потому что оно позволяет вам создавать базу кода, созданную файлами make и другими системами сборки, а также проектами IDE, такими как Visual Studio. Это дает вам единую точку определения, которая может сопровождаться комментариями (я фанат doxygen, который позволяет вам генерировать макро документацию).

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

Ответ 8

Глобальные, общие по проекту константы, которые являются целевыми, лучше всего использовать в CCFLAGS в вашем файле makefile. Константы, которые вы используете повсюду, могут входить в соответствующие файлы заголовков, которые включены в любой файл, который их использует.

Например,

// bool.h - a boolean type for C
#ifndef __BOOL_H__
#define BOOL_H
typedef int bool_t
#define TRUE 1
#define FALSE 0
#endif
код >

Затем в другом заголовке


`#include "bool.h"`
// blah