Разница в определении и объявлении не обнаружена gcc?

Я создаю программное обеспечение с помощью 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, просто странно, что об этом нет предупреждения).

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

Some programmer dude 23.07.2024 11:02

Действительно, конфликта времени компиляции нет. Поэтому отсутствие предупреждения о несоответствии определения/объявления имеет смысл.

Guillaume Petitjean 23.07.2024 11:52

Почему у вас есть определение в заголовочном файле?!

n. m. could be an AI 23.07.2024 13:50

@n.m.couldbeanAI это редкость, но не запрещено. Цель состоит в том, чтобы контролировать, какой файл .c использует какую переменную. Используется в критически важных системах безопасности.

Guillaume Petitjean 23.07.2024 16:34
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
87
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

Вы показываете объявление b_myBool в header1.h2, но не показываете 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 в модуле реализации, где эта переменная фактически определена.

Другие вопросы по теме