Я создаю программное обеспечение с помощью Arm-none-eabi-gcc 10.2 (-01, -Wall)
У меня есть следующие файлы (очевидно, это не те файлы, которые мне не разрешено публиковать):
заголовок1.h
// variable definition
bool b_myBool;
Тип bool
переобъявлен в моем программном обеспечении на unsigned char
header2.h
// variable declaration, const is to be sure the variable is not modified in this translation unit
extern const bool b_myBool;
файл2.c
#include <header2.h>
void myFunction(void)
{
bool l_localBool;
...
if (l_localBool != b_myBool)
{
do something;
}
}
Теперь я случайно изменил header2.h так:
extern const float b_myBool;
И я не получил никакого предупреждения (несмотря на опцию компиляции -Wall
), ни потому, что тип объявления и определения не совпадают, ни потому, что операнды сравнения имеют разные типы (я знаю, что сравнение чисел с плавающей запятой и целых чисел определяется стандартом, поскольку int будет преобразован в float, просто странно, что об этом нет предупреждения).
Действительно, конфликта времени компиляции нет. Поэтому отсутствие предупреждения о несоответствии определения/объявления имеет смысл.
Почему у вас есть определение в заголовочном файле?!
@n.m.couldbeanAI это редкость, но не запрещено. Цель состоит в том, чтобы контролировать, какой файл .c использует какую переменную. Используется в критически важных системах безопасности.
Наиболее распространенная модель, используемая для реализации C, заключается в том, что каждая единица перевода1 компилируется отдельно, и в объектные модули, создаваемые в результате компиляции, включается мало информации о типах символов или она вообще отсутствует. Когда компоновщик связывает вместе разные объектные модули, он не может выполнять проверку типов, поскольку информация о типе отсутствует в объектных модулях.
Вы показываете объявление b_myBool
в header1.h
2, но не показываете header1.h
, включенное в какой-либо другой файл, особенно в file2.c
. Поскольку header1.h
не включен в file2.c
, а header2.h
есть, компилятор не видит объявление в header1.h
во время компиляции file2.c
и, следовательно, не может сверить объявление в header1.h
с объявлением в header2.h
.
Если вам необходим внешне связанный объект, большинство программистов на C используют следующую практику:
foo.c
.foo.h
.#include
, чтобы включить foo.h
в foo.c
и в каждый исходный файл, использующий этот объект.Тот факт, что foo.h
включен в foo.c
, означает, что компилятор увидит и определение в исходном файле, и объявление в заголовке во время одной и той же компиляции, поэтому он может и будет проверять тип. Тогда тот факт, что foo.h
включен в каждый другой исходный файл, которому нужен объект, означает, что он получит то же объявление, которое проверяется при компиляции foo.c
.
1 Единица перевода — это весь компилируемый исходный код, полученный из одного файла исходного кода, в котором все его #include
операторы заменены включенными заголовочными файлами.
2 Каждое определение — это декларация. bool b_myBool;
— это предварительное определение. Предварительное определение на самом деле не является определением, но может привести к созданию определения.
И я не получил никакого предупреждения (несмотря на опцию компиляции -Wall), ни потому, что тип объявления и определения не совпадают, ни потому, что операнды сравнения имеют разные типы (я знаю, что сравнение float и int определяется стандартом, поскольку int будет преобразовано в число с плавающей запятой, просто странно, что об этом нет предупреждения).
Нет, это не так, проблема в том, что вы используете в своем коде два разных определения (в двух отдельных заголовочных файлах) и изменили тип переменной только в одной единице компиляции (в единице компиляции, которая определена в неправильный заголовочный файл).
Для решения этой проблемы были введены заголовочные файлы, но компилятор, компилирующий объявление extern
, не может узнать, что существует другая единица компиляции, в которой переменная определена иначе.
файлы заголовков делают объявление extern
общим для кода клиента и кода сервера (сервера самой переменной), чтобы иметь общее объявление, которое является общим. Ваша проблема в том, что вы используете два разных файла заголовков с разными объявлениями переменной. Компилятор не знает ничего, что было скомпилировано отдельно, поэтому вы не получите даже предупреждения, потому что вы сообщили компилятору, что в этой компиляции переменная определена по-другому (но определена в другом месте, а не здесь).
Компилятору необходимо знать тип переменной в обоих файлах... но вы использовали разные объявления, поскольку они находятся в разных файлах заголовков, которые не включены в пересекающиеся единицы компиляции.
Даже если вы скомпилируете оба модуля компиляции в одной и той же командной строке, эти две компиляции независимы, что делает это разное объявление невидимым при одной компиляции при другой. Если вы хотите, чтобы функция была объявлена в каком-либо модуле компиляции и была доступна только в режиме только для чтения из остальной части юниверса, объявите функцию доступа, которая дает вам копию значения (ну, вы можете прочитать ее и изменить прочитанное значение, но вы не можете изменить источник) и объявите переменную static
в модуле реализации, где эта переменная фактически определена.
Знаете ли вы о понятии единиц перевода? Сам компилятор имеет дело только с единицами перевода, которые, короче говоря, представляют собой единый источник со всем включенным в него заголовочным файлом. Если у вас есть один глобальный символ, объявленный по-разному в разных единицах перевода, то поведение будет неопределенным. К сожалению, компилятор или компоновщик ничего не могут обнаружить.