Заголовок гвардии и LNK4006 - PullRequest
       5

Заголовок гвардии и LNK4006

10 голосов
/ 21 января 2010

У меня есть массив символов, определенный в заголовке

//header.h
const char* temp[] = {"JeffSter"};

Заголовок, если #defined защищен и имеет #pragma один раз наверху. Если этот заголовок включен в нескольких местах, я получаю LNK4006 - char const * * temp, уже определенный в blahblah.obj. Итак, у меня есть пара вопросов по этому поводу

  1. Почему это происходит, если у меня установлены охранники? Я думал, что они препятствовали чтению заголовка после первого доступа.
  2. Почему многочисленные перечисления в этом заголовке также не дают предупреждений LNK4006?
  3. Если я добавлю статический элемент перед подписью, я не получу предупреждение. Каковы последствия того, чтобы делать это таким образом.
  4. Есть ли лучший способ сделать это, чтобы избежать ошибки, но позвольте мне объявить массив в заголовке. Я бы очень не хотел иметь файл cpp только для определения массива.

Ответы [ 5 ]

14 голосов
/ 21 января 2010

Почему это происходит, если у меня установлены охранники? Я думал, что они препятствуют тому, чтобы заголовок был прочитан после первого доступа.

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

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

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

Почему многочисленные перечисления в этом заголовке также не дают предупреждений LNK4006?

Потому что они не определяют «глобальные переменные», они являются только объявлениями о типах и т. Д. Они не резервируют никакого хранилища.

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

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

static int i;

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

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

Если вы хотите, чтобы массив был одним объектом, видимым из всех ваших файлов C ++, вы должны сделать:

extern int array[SIZE];

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

int array[SIZE];

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

По сути, extern сообщает компилятору, что "array определено где-то и имеет тип int и размер SIZE". Затем вы на самом деле определяете array только один раз. На этапе соединения все разрешается красиво.

4 голосов
/ 21 января 2010
  1. Защита заголовка не имеет абсолютно никакого отношения к предотвращению множественных определений в вашей всей программе . Цель средств защиты заголовков состоит в том, чтобы предотвратить многократное включение одного и того же заголовочного файла в одну и ту же единицу перевода (файл .cpp). Другими словами, они существуют для предотвращения нескольких определений в одного и того же исходного файла . И они работают так, как задумано в вашем случае.

  2. Правило, регулирующее проблемы множественного определения в C ++, называется One Definition Rule (ODR). ODR определяется по-разному для разных типов объектов. Например, типы могут иметь несколько идентичных определений в программе. Они могут (и чаще всего имеют to) быть определены в каждой единице перевода, где они используются. Вот почему определение enum не приводит к ошибке.

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

  3. Добавляя static, вы даете объекту внутреннюю связь . Это заставит ошибку исчезнуть, так как теперь это совершенно нормально с точки зрения ODR. Но это определит независимый temp объект в каждой единице перевода, в которую включен ваш заголовочный файл. Для достижения того же эффекта вы также можете сделать

    const char* const temp[] = { "JeffSter" }; 
    

    , поскольку const объекты в C ++ имеют внутреннюю связь по умолчанию.

  4. Это зависит от того, нужен ли вам объект с внешней связью (то есть один для всей программы) или объект с внутренней связью (уникальный для каждой единицы перевода). Если вам нужен последний, используйте static и / или дополнительные const (если это работает для вас), как показано выше.

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

    extern const char* temp[];
    

    и переместите определение в один и только один файл .cpp

    char* const temp[] = { "JeffSter" }; 
    

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

    extern const char* temp[1];
    

    и не забудьте синхронизировать его между объявлением и определением.

4 голосов
/ 21 января 2010

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

static const char* temp1[] = {"JeffSter"};
// or
namespace {
    const char* temp2[] = {"JeffSter"};
}

В качестве альтернативы вы можете использовать один исходный файл, который определяет temp и просто объявить его как extern в заголовке:

// temp.cpp:
const char* temp[] = {"JeffSter"};

// header.h:
extern const char* temp[];
0 голосов
/ 16 июля 2016

Я с уважением не согласен с советом против определения переменных в заголовках, потому что я думаю, что "никогда" не является слишком широким. Тем не менее, эпизод, который привел меня к этой теме, предлагает предостерегающую историю для тех, кто осмеливается это сделать.

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

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

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

0 голосов
/ 21 января 2010

Погоди ... ты перепутал свои объявления ... ты сказал 'char const * * temp', но в заголовочном файле у тебя есть 'const char * temp [] = {"JeffSter"};'.

См. Раздел 6.1 C FAQ , в разделе «Массивы и указатели», цитата:

6.1:    I had the definition char a[6] in one source file, and in
    another I declared extern char *a.  Why didn't it work?

A:  In one source file you defined an array of characters and in the
    other you declared a pointer to characters.  The declaration
    extern char *a simply does not match the actual definition.
    The type pointer-to-type-T is not the same as array-of-type-T.
    Use extern char a[].

    References: ISO Sec. 6.5.4.2; CT&P Sec. 3.3 pp. 33-4, Sec. 4.5
    pp. 64-5.

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

Надеюсь, это поможет, С наилучшими пожеланиями, Том.

...