Множественная ошибка определения переменной, которая объявлена ​​и определена в файле заголовка и используется только в ее файле cpp.

Я нахожусь в процессе переноса кода, написанного для компиляции для одного чипа, на другой чип.

Одна из возникших проблем — это множество множественных ошибок определения. Некоторые из них, по-видимому, связаны с тем, что компоновщик для первого чипа позволяет мне лениться с объявлением переменных extern, когда они должны использоваться в нескольких исходных файлах. Раньше я вообще не использовал extern (объявлял и определял переменную в something.h, использовал ее в something.cpp и других исходных файлах, которые включали something.h), и он отлично скомпилировался и линковался.

Думаю, я исправил эти проблемы достаточно хорошо: теперь мои переменные, которые являются общими, имеют этот шаблон:

Что-то.ч

extern int foo;

Что-то.cpp

int foo = 0;

//using foo to do stuff

Main.cpp

#include "Something.h"

//using foo to do stuff

Все хорошо.

Вот немного, что я не понимаю, и не могу найти ответы ни здесь, ни в Google. Я заметил одни и те же множественные ошибки определения, вызванные переменными, объявленными и определенными в Something.h и используемыми только в Something.cpp.

У Something.h есть защита включения, поэтому я не думаю, что это связано с тем, что Something.h несколько раз включался где-то в моей программе.

Ошибка исчезнет, ​​если я объявлю ее как extern и определю в файле cpp, но мне это кажется неправильным. Я считаю, что extern не нужен для связи переменной между Something.h и Something.cpp.

Буду очень признателен за любые советы, мне бы очень хотелось понять, чего мне здесь не хватает.

(Кстати, я компилирую для ESP32 с помощью Arduino IDE.)


person JRVeale    schedule 19.03.2019    source источник
comment
Входит ли Something.h только в Something.cpp или, возможно, также и в SomeOtherThing.cpp? (Подумайте о том, что происходит, когда вы компилируете разные единицы перевода, использующие один и тот же заголовок.)   -  person Mat    schedule 19.03.2019
comment
Кажется, я начинаю понимать, что вы имеете в виду. Я предположил, что использование защиты включения предотвратит эту проблему, но вы предполагаете, что, поскольку каждый файл компилируется отдельно перед связыванием, защита включения вообще не помогает? Я не уверен, что понимаю, почему это так, потому что тогда я не вижу ситуации, в которой защита включения действительно работает?   -  person JRVeale    schedule 19.03.2019
comment
Защита включения работает для каждого исходного файла с #include "Something.h" внутри. Guard будет эффективен только в том случае, если у вас есть несколько (вероятно, косвенных) #include "Something.h" в одном файле. Каждый источник создает экземпляр одной и той же переменной.   -  person harper    schedule 19.03.2019
comment
А, конечно, спасибо!   -  person JRVeale    schedule 19.03.2019


Ответы (3)


Если вы объявите свою переменную в заголовочном файле:

#ifndef GLOBAL_H
#define GLOBAL_H

int foo = 0;

#endif

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

// .h
extern int foo;

// .cpp
int foo = 0

Еще один способ С++ сделать это может быть примерно таким:

#ifndef GLOBAL_H
#define GLOBAL_H

struct Global {
    static int foo;
};
#endif

И в вашем файле cpp:

#include "variables.h"

int Global::foo = 0;

С++ 17 устраняет эту проблему с помощью встроенных переменных, поэтому вы можете сделать:

#ifndef GLOBAL_H
#define GLOBAL_H

inline int foo = 0;

#endif

Дополнительные сведения см. в разделе Как работают встроенные переменные?.

person mohabouje    schedule 19.03.2019
comment
Большое спасибо, особенно за предложения по правильному стилю. Вы больше C++ убеждаете меня в чувстве, которое у меня было какое-то время, что этот заголовок требует меньше глобальных переменных и больше инкапсуляции в классе - person JRVeale; 19.03.2019
comment
Как насчет этого, если я пишу код на C и хочу определить массив со строками сообщений об ошибках? Для меня естественно хранить определение строки чуть ниже определения перечисления, а последнее должно храниться в h-файле, чтобы его можно было использовать снаружи. Является ли мой путь плохой практикой? Что самое лучшее? - person massi; 18.11.2020

Ошибка исчезнет, ​​если я объявлю ее как extern и определю в файле cpp,

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

Чтобы решить эту проблему, вам нужно либо создать ее в анон. пространство имен

Что-то.ч

namespace {
  int foo = 0;
}

Или используйте статическое ключевое слово

Что-то.ч

 static int foo = 0;

Оба будут создавать разные переменные в каждой единице компиляции.

person darune    schedule 19.03.2019

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

person Ishara Priyadarshana    schedule 07.01.2021