Extern c описание: Программирование на C и C++
Содержание
Программирование на C и C++
Поскольку С позволяет выполнять раздельную компиляцию модулей для большой программы в целях ускорения компиляции и помощи управлению большими проектами, должны быть способы передачи информации о глобальных переменных файлам программы. Решение заключается в объявлении всех глобальных переменных в одном файле и использовании при объявлении в других файлах слова extern, как показано в таблице
Файл 1 | Файл 2 |
---|---|
int х, у; char ch; main(void) void func1 (void) | extern int x, y; extern char ch; void func22(void) void func23(void) |
В файле 2 список глобальных переменных копируется из файла 1 и при объявлении добавляется спецификатор extern. Спецификатор extern сообщает компилятору, что следующие за ним типы и имена переменных объявляются где-то в другом месте. Другими словами, extern позволяет компилятору знать о типах и именах глобальных переменных без действительного создания этих переменных. Когда два модуля объединяются, все ссылки на внешние переменные пересматриваются.
Если при объявлении выделяется память под переменную, то процесс называется определением. Использование extern приводит к объявлению, но не к определению. Оно просто говорит компилятору, что определение происходит где-то в другом месте программы.
Имеется другой вариант использования extern. Когда используется глобальная переменная внутри функции, находящейся в том же файле, где происходит объявление глобальной переменной, то можно объявлять ее как extern, хотя делать это не обязательно. Следующий фрагмент программы демонстрирует, как использовать данный вариант.
int first, last; /*глобальное определение first и last */
int main (void)
{
extern int first; /* необязательное использование extern объявления */
……
}
Хотя объявление переменной с extern может иметь место в одном файле с объявлением глобальной переменной, в этом на самом деле нет необходимости. Если компилятор С встречает переменную, которая не была объявлена, то компилятор проверяет, соответствует ли она какой-либо глобальной переменной. Если это так, то компилятор предполагает, что эта переменная ссылается на глобальную.
Использование volatile, extern, register, auto, mutable в C++ (обзор)
В языке C++ есть квалификаторы и спецификаторы, которые в связи с их не очень частым использованием могут вызвать замешательство у неискушённых и начинающих. В данном коротком очерке я собираюсь пролить свет на данную тему.
Начнём с квалификатора volatile
Если подходить формально то квалификатор volatile информирует компилятор что переменная может быть изменена не явным способом т.е. без явного использовать оператора присвоения. И что же это означает на практике? Дело в том, что как правило компиляторы автоматически применяют оптимизацию, предполагая что значение переменной остаётся постоянным, если оно не указано с левой стороны от оператора присваивания. Т.е. если переменная не меняется, то и нет нужды проверять её при каждом обращении.
Пример:
bool exit = true;
while( exit )
{
};
В данном случае значение переменной exit будет прочитано только один раз. На первый взгляд в этом не ничего страшного. Однако, если содержание переменно exit может изменять не только код текущего потока, но допустим другой поток (опустим сейчас вопросы атомарности операции) или вообще некое внешние устройство, то мы можем получить бесконечный цикл коим он по нашей задумке быть не должен.
Но если мы перед объявлением переменной поставим квалификатор volatile, то переменная будет считываться при каждой проверке.
Пример:
volatile bool exit = true;
while( exit )
{
};
Приведу более жизненный пример. Предположим есть необходимость работы с внешнем устройством через некоторый порт. Нам нужно записать в порт последовательность из трёх нулей.
unsigned char* pControl = 0xff24 ;
*pControl = 0 ;
*pControl = 0 ;
*pControl = 0 ;
После оптимизации вместо трёх нулей будет записан только один. Более того после оптимизации прочитать что либо из порта будет не реально т.к. изменения переменной будут происходить во внешней среде.
Герберт Шилдт приводит такой пример: Например, адрес глобальной переменной можно передать таймеру операционной системы и использовать его для отсчёта времени. В этом случае содержимое переменной изменяется без явного выполнения какого-либо оператора присвоения.
Чисто теоретически если быть стопроцентно уверенным в атомарности операции, то можно использовать квалификатор volatile для взаимодействие переменной между потоками без использования дополнительных объектов синхронизации таких как мьютексы, но поскольку такую уверенность может дать разве что char, то применять volatile в этих целях будет не корректным.
Применять volatile в дополнение к мьютексам нет необходимости, хотя на эту тему сломано много копий, но примеров реальных компиляторов и ОС, где мьютексов мало, мне не встречалось.
Кстати, функции-члены тоже могут объявляться со словом volatile:
class A
{
public:
void foo() volatile;
}
В данном случае внутри foo() указатель this так же имеет атрибут volatile.
Спецификатор extern
В языках с\с++ существуют внутренние связи, внешние связи и отсутствие связей. Глобальные переменные имеют внешние связи и это позволяет получить доступ к ним из любой части программы. Если к глобальным переменным добавить спецификатор static, то глобальные переменные утратят внешние связи и будут иметь только внутренние связи, т.е. будут доступны только внутри файла, в котором они были описаны. Локальные переменные не имеют связей и поэтому доступны только внутри блока где они были описаны.
Спецификатор extern указывает, что переменная обладает внешними связями. Дело в том, что надо различать определение и объявление. Объявление указывает имя объекта и его тип, то где как определение выделяет под объект память. Таким образом можно сделать несколько объявлений объекта и только одно определение. В большинстве случаев, определение и объявление совпадают. Спецификатор extern позволяет объявить переменную без её определения т.е без выделения памяти. Используя спецификатор extern можно путём объявления обратиться к переменной, определённой в другом месте. К примеру, можно определить все глобальные переменные в одном файле, а в других файлах получать к ним доступ через объявление со спецификатором extern.
Спецификатор register
Изначально это спецификатор применялся только к переменным типа char и int, но теперь его можно применять к переменным любого типа. Данный спецификатор указывает компилятору хранить значение переменной не в памяти, а в регистре процессора. Иной трактовкой спецификатора register служит подсказка компилятору, что данный объект используется очень интенсивно. Разумеется в регистрах смогут поместиться только данные весьма ограниченного объёма, такие как int и char, а боле крупные объекты в регистры не поместятся, но получат более высокой приоритет обработки. Надо учитывать, что register это рекомендация такая же как inline и компилятор может просто игнорировать спецификатор register и обрабатывать переменную как обычно.
Так же замечу, что спецификатор register можно применять только к локальным переменным и формальным параметрам.
bool foo(register char ch)
{
register bool bRes;
return bRes;
}
Компилятор автоматически преобразует лишние регистровые переменные в обычные, поэтому нет смысла в подсчёте количества регистровых переменных что обеспечивает машино-независимость.
Ключевое слово auto
Судьбу этого ключевого слово можно сравнить с goto: с одной стороны — в языке есть, с другой — его не используют. Но в случае с auto всё проще. Хотя его и можно использовать для объявления локальных переменных, но смысла в этом нет, так как все локальные переменные по умолчанию считаются автоматическими. Поэтому на практике это ключевое слово не используется. Есть мнение что ключевое слово auto включили в язык С для совместимости с языком B ну а потом оно перекочевало и в С++
Ключевое слово mutable
Иногда есть необходимость изменить некий объект внутри класса, гарантируя неприкосновенность остальных элементов. Неприкосновенность можно гарантировать при помощи const, однако const запрещает изменение всего.
class Exm
{
int a;
int b;
public:
int getA() const
{
return a; // все правильно
}
int setA(int i) const
{
a = i;// ошибка доступа
}
}
Помочь в данном случае может определение переменной а с ключевым словом mutable. Внесём исправление в приведённый чуть выше пример:
class Exm
{
mutable int a; // добавили в объявление ключевое слово mutable
// позволяющие игнорировать модификатор const
// по отношению к данной переменной
int b;
public:
int getA() const //
{
return a; // все правильно
}
int setA(int i) const
{
a = i;// теперь всё правильно. Мы можем изменять переменную а
b = i; // Ошибка! Переменная b по прежнему не доступна для изменения.
}
}
————
Александр Бабашов (tellurian)
METTLER TOLEDO Весы для лаборатории, производства и торговли
Измерительные приборы — это оборудование, используемое для точного определения различных параметров исследуемых объектов. Наша компания занимается …
Измерительные приборы — это оборудование, используемое для точного определения различных параметров исследуемых объектов. Наша компания занимается производством и обслуживанием контрольно-измерительных приборов и весового оборудования для различных отраслей промышленности.
Предлагаем купить измерительные приборы для оптимизации технологических процессов, повышения производительности и снижения затрат. Точные инструменты позволят установить соответствие нормативным требованиям.
Мы осуществляем продажу измерительных приборов, предназначенных для исследовательской деятельности и научных разработок, производства продукции и контроля качества, логистики и розничной торговли. МЕТТЛЕР ТОЛЕДО предлагает следующие измерительные приборы для различных областей применения:
Лабораторное оборудование
Для научных и лабораторных исследований требуются высокоточные измерительные и аналитические приборы и системы. Они используются для взвешивания, анализа, дозирования, автоматизации химических процессов, измерения физических и химических свойств, концентрации газов, плотности, спектрального анализа веществ и рефрактометрии, химического синтеза, подготовки проб, реакционной калориметрии, анализа размеров и формы частиц. Специализированное программное обеспечение позволяет управлять процессами и получать наглядное отображение данных.
Лабораторное оборудование включают следующие системы:
Промышленное оборудование
Если вас интересуют промышленное измерительное оборудование, предлагаем купить подходящие системы для взвешивания, контроля продукции, решения логистических задач и транспортировки грузов. Используйте точные приборы для стандартного и сложного дозирования, взвешивания в сложных условиях и взрывоопасной среде. Обеспечьте точность результатов с помощью поверочных гирь и тестовых образцов. Подключение периферийных устройств к приборам позволит регистрировать результаты и параметры взвешивания. Программное обеспечение с понятным интерфейсом оптимизирует процессы посредством управления оборудованием с ПК.
Ассортимент промышленных контрольно-измерительных приборов и инструментов включает:
Весы для магазинов и оборудование для розничной торговли
В сфере розничной торговли продовольственными товарами необходимы измерительные приборы и оборудование для взвешивания и маркировки товаров. Используйте весы для решения типовых задач, печати чеков и быстрого взвешивания, разгружающего поток покупателей. В сложных ситуациях пригодятся специализированные весовые системы с нетребовательным обслуживанием и уходом. ПО и документация упростят настройку системы и обучение персонала.
Вниманию покупателей предлагаются следующее оборудование для торговли:
Как купить весы МЕТТЛЕР ТОЛЕДО?
Чтобы купить оборудование на нашем сайте, оформите запрос в режиме онлайн в соответствующем разделе. Уточните задачу, которая должна быть решена с помощью требуемого прибора. Укажите контактные данные: страну, город, адрес, телефон, e-mail, название предприятия. Заполненная форма направляется специалисту компании, который свяжется с вами для уточнения ключевых моментов.
Сеть представительств METTLER TOLEDO для обслуживания и сервисной поддержки распространена по всему миру. В России отдел продаж и сервиса расположен в Москве. Региональные представительства по продажам находятся также в Казани, Ростове-на-Дону, Самаре, Екатеринбурге, Красноярске, Уфе, Хабаровске, Новосибирске.
Отправьте отзыв, задайте вопрос специалисту, свяжитесь с конкретным отделом. Воспользуйтесь онлайн-формой обратной связи или позвоните по указанному телефону офиса в выбранном регионе. Консультанты ответят на каждое обращение и вышлют коммерческое предложение по индивидуальному запросу.
Приглашаем на День открытых дверей НГАСУ (Сибстрин)! 30 октября 2021 года (суббота) вы сможете познакомиться с НГАСУ (Сибстрин), узнать все об образовательных программах, выбрать востребованную профессию и получить ответы на свои вопросы на Дне открытых дверей!
|
Началась Всероссийская перепись населения Уважаемые сотрудники и студенты НГАСУ (Сибстрин)!
|
В университете стартует Декада здоровья С 18 по 29 октября 2021 года в Новосибирском государственном архитектурно-строительном университете (Сибстрин) пройдет Декада здоровья. Центр по внеучебной и воспитательной работе и движение Молодежная инициатива подготовили насыщенную программу для студентов, преподавателей и сотрудников НГАСУ (Сибстрин).
|
|
|
Языковая привязка — cppreference.com
Обеспечивает связь между модулями, написанными на разных языках программирования.
extern строковый литерал { декларация-seq (необязательно) } | (1) | ||||||||
extern объявление строкового литерала | (2) | ||||||||
1) Применяет строковый литерал спецификации языка ко всем типам функций, именам функций с внешней связью и переменным с внешней связью, объявленным в декларации-seq.
2) Применяет строковый литерал спецификации языка к одному объявлению или определению.
строковый литерал | — | Имя требуемой языковой связи. |
декларация seq | — | последовательность объявлений, которая может включать в себя вложенные спецификации связи. |
декларация | — | декларация |
[править] Объяснение
Каждый тип функции, каждое имя функции с внешней связью и каждое имя переменной с внешней связью имеет свойство, называемое языковая связь .Связывание языков инкапсулирует набор требований, необходимых для связывания с модулем, написанным на другом языке программирования: соглашение о вызовах, алгоритм изменения имени и т. Д.
Гарантированно поддерживаются только две языковые связи:
- «C ++», языковая связь по умолчанию.
- «C», который позволяет связываться с функциями, написанными на языке программирования C, и определять в программе C ++ функции, которые могут быть вызваны из модулей, написанных на C.
extern "C" { int open (const char * pathname, int flags); // Объявление функции C } int main () { int fd = open ("test.txt", 0); // вызывает функцию C из программы C ++ } // Эта функция C ++ может быть вызвана из кода C extern "C" void handler (int) { std :: cout << "Обратный вызов вызван \ n"; // Может использовать C ++ }
Поскольку языковая привязка является частью каждого типа функции, указатели на функции также поддерживают языковую привязку. Языковая связь типов функций (которая представляет собой соглашение о вызовах) и языковая связь имен функций (которая представляет собой искажение имен) не зависят друг от друга:
extern "C" void f1 (void (* pf) ()); // объявляет функцию f1 со связью C, // который возвращает void и принимает указатель на функцию C // который возвращает void и не принимает параметров extern «C» typedef void FUNC (); // объявляет FUNC как тип функции C, который возвращает void // и не принимает параметров FUNC f2; // имя f2 связано с C ++, но его тип - функция C внешний "C" FUNC f3; // имя f3 связано с C, а его тип - C function void () пустота (* pf2) (FUNC *); // имя pf2 связано с C ++, и его тип // "указатель на функцию C ++, которая возвращает void и принимает единицу // аргумент типа 'указатель на функцию C, которая возвращает void // и не принимает параметров '" extern "C" { static void f4 (); // имя функции f4 имеет внутреннюю привязку (без языка) // но тип функции связан с языком C }
Две функции с одним и тем же именем и одним и тем же списком параметров в одном пространстве имен не могут иметь две разные языковые связи (обратите внимание, однако, что привязка параметра может допускать такую перегрузку, как в случае std :: qsort и std :: bsearch).Точно так же две переменные в одном пространстве имен не могут иметь две разные языковые связи.
[править] Специальные правила для связи "C"
- Когда объявления членов класса и объявления типов функций-членов появляются в языковом блоке «C», их связь остается «C ++».
- Когда две функции с одним и тем же неквалифицированным именем объявлены в разных пространствах имен, и обе имеют языковую связь «C», объявления относятся к одной и той же функции.
- Когда две переменные со связью на языке «C» и одним и тем же именем появляются в разных пространствах имен, они ссылаются на одну и ту же переменную.
- Переменная «C» и функция «C» не могут иметь одно и то же имя, независимо от того, определены ли они в одном или разных пространствах имен.
[править] Примечания
Спецификации языка могут появляться только в области пространства имен.
Фигурные скобки в спецификации языка не определяют объем.
При вложении языковых спецификаций действует самая внутренняя спецификация.
Функция может быть повторно объявлена без спецификации связывания после того, как она была объявлена со спецификацией языка, второе объявление будет повторно использовать связывание первого языка.Обратное неверно: если первое объявление не имеет языковой связи, предполагается, что это "C ++", и повторное объявление с другим языком является ошибкой.
Объявление, непосредственно содержащееся в спецификации языковой связи, обрабатывается так, как если бы оно содержало спецификатор extern
с целью определения связи объявленного имени и того, является ли оно определением.
extern "C" int x; // объявление, а не определение // Вышеупомянутая строка эквивалентна extern "C" {extern int x; } extern "C" {int x; } // объявление и определение extern "C" double f (); статический двойной f (); // ошибка: конфликт ссылок extern "C" static void g (); // ошибка: конфликт ссылок
extern "C"
позволяет включать файлы заголовков, содержащие объявления функций библиотеки C, в программу C ++, но если тот же файл заголовка используется совместно с программой C, extern "C"
(что не допускается в C) должен быть скрыт с помощью соответствующего
#ifdef, обычно __cplusplus:
#ifdef __cplusplus extern «C» int foo (int, int); // Компилятор C ++ видит это #еще int foo (интервал, интервал); // Компилятор C видит это #endif
Единственный современный компилятор, который различает типы функций с языковыми связями "C" и "C ++", - это Oracle Studio, другие не допускают перегрузки, которые отличаются только языковой связью, включая наборы перегрузки, требуемые стандартом C ++ (std :: qsort , std :: bsearch, std :: signal, std :: atexit и std :: at_quick_exit): | Ошибка GCC 2316, ошибка Clang 6277, CWG1555.
extern «C» с использованием c_predfun = int (const void *, const void *); extern «C ++» с использованием cpp_predfun = int (const void *, const void *); // неверно сформирован, но принимается большинством компиляторов static_assert (std :: is_same:: значение, «Связи языков C и C ++ не должны различать типы функций.»); // следующие объявления не объявляют перегрузки в большинстве компиляторов // потому что c_predfun и cpp_predfun считаются одного типа void qsort (void * base, std :: size_t nmemb, std :: size_t size, c_predfun * compare); void qsort (void * base, std :: size_t nmemb, std :: size_t size, cpp_predfun * compare);
[править] Ссылки
- Стандарт C ++ 20 (ISO / IEC 14882: 2020):
- 9.11 Характеристики связи [dcl.link]
- Стандарт C ++ 17 (ISO / IEC 14882: 2017):
- 10.5 Характеристики связи [dcl.link]
- Стандарт C ++ 14 (ISO / IEC 14882: 2014):
- 7,5 Характеристики связи [dcl.link]
- Стандарт C ++ 11 (ISO / IEC 14882: 2011):
- 7.5 Характеристики рычажного механизма [dcl.ссылка]
- Стандарт C ++ 03 (ISO / IEC 14882: 2003):
- 7,5 Характеристики связи [dcl.link]
Требуется ли также добавить «extern C» в исходный файл?
Поскольку вы имеете в виду
внешний "C" {...}
, они объявляют, что некоторые функции имеют связь "C", а не связь "C ++" (которая обычно имеет кучу дополнительных украшений имен для поддержки таких вещей, как перегруженные функции).
Цель, конечно же, состоит в том, чтобы позволить коду C ++ взаимодействовать с кодом C, который обычно находится в библиотеке. Если заголовки библиотеки были написаны не с учетом C ++, , тогда они не будут включать защиту extern «C»
для C ++ .
Заголовок C, написанный с учетом C ++, будет включать что-то вроде
#ifdef __cplusplus
extern "C" {
#endif
...
#ifdef __cplusplus
}
#endif
, чтобы убедиться, что программы на C ++ видят правильную связь.Однако не все библиотеки были написаны с учетом C ++, поэтому иногда приходится делать
extern "C" {
#include "myclibrary.h"
}
, чтобы установить правильную связь. Если файл заголовка предоставлен кем-то другим, то его не рекомендуется изменять (потому что тогда вы не сможете легко его обновить), поэтому лучше обернуть файл заголовка собственной защитой (возможно, в вашем собственном файле заголовка).
extern «C»
не является (AFAIK) ANSI C, поэтому не может быть включен в нормальный код C без защиты препроцессора.
В ответ на ваше изменение:
Если вы используете компилятор C ++ и объявляете функцию как extern «C» в файле заголовка, вам не нужно также объявлять эту функцию как extern «C» в файле реализации. Из раздела 7.5 стандарта C ++ (выделено мной):
Если два объявления одного и того же
функция или объект указать разные
спецификации связи (то есть
характеристики связи этих
в декларациях указываются разные
строковые литералы), программа
некорректно, если декларации появляются
в той же единице перевода, а
одно правило определения
применяется, если объявления появляются в
разные единицы перевода.Кроме
для функций со связью C ++, a
объявление функции без привязки
спецификация не должна предшествовать
первая спецификация связи для этого
функция. Функция может быть объявлена
без спецификации связи после
явная спецификация связи имеет
был замечен; связь явно
указанные в более ранней декларации
не зависит от такой функции
декларация.
Я не уверен, что это хорошая практика, поскольку существует вероятность случайного расхождения спецификаций связывания (если, например, файл заголовка, содержащий спецификацию связывания, не включен в файл реализации).Я думаю, что лучше указать в файле реализации явно.
[решено] В чем трюк с использованием этого ключевого слова "extern" в C ++?
Важно понимать, что
extern Pub * gpub;
- это не объявление переменной, а объявление внешнего объекта. Чтобы создать действующую программу, вам понадобится фактическое объявление (то есть без extern) где-нибудь еще.
Если ваша программа состоит только из:
int a; int main () { а = 1; вернуть; }
, тогда все хорошо, потому что первая строка - это (нормальное) объявление.
Если ваша программа состоит из
extern int a; int main () { а = 1; вернуть; }
тогда компоновщик будет жаловаться, что не может найти никакого объявления a, и, следовательно, не знает, как указать адрес памяти, который основная программа должна использовать при обращении к a. (компилятор этого не знает - он только заботится о том, чтобы знать, что a является объектом типа int)
Обратите внимание, что использование extern действительно имеет смысл только тогда, когда вы создаете библиотеку, которая используется другой программой.Если используется в библиотеке, оператор extern int a; скажет компоновщику создать внешнюю связь с объектом a, чтобы фактическая программа знала, где найти этот объект.
Итак, еще один способ исправить второй пример кода - создать библиотеку mylib.lib с файлом заголовка, содержащим
"extern int a;"
и исходный файл, содержащий
#includeint a = 1;
, а затем напишите свою основную программу как
#include// содержит объявление extern int main () { вернуть; }
Эта настройка сообщит компилятору тип файла, а компоновщик будет знать, что объявление может не содержаться в вашем main.exe. Связывание main.exe с mylib.lib позволяет компоновщику найти объявление внешней связи в mylib.lib.
Компиляция и опасность: неправильное использование ключевого слова extern в C
C не является языком со строгой типизацией, но он может выполнять некоторые базовые проверки. Зная это, разработчики C обычно осторожны, потому что проблемы типа отладки раздражают и могут быть ограничены некоторыми простыми передовыми практиками.
Иногда скучающий разработчик пробует что-нибудь смелое, что-то новое. Здесь мы подробно рассмотрим один из этих экспериментов, с которыми я недавно столкнулся в устаревшем проекте: что, если мы будем использовать ключевое слово extern
без заголовков?
Это редкость, но может быть, не так уж и плохо? Посмотрим…
Программа испытаний
Начнем с простой программы, печатающей номер его версии. версия
объявлена в основном файле и определена в файле версии.
Я гарантирую, что и объявление, и определение относятся к одному типу. В любом случае, если что-то не так, компилятор хотя бы выдаст предупреждение, верно?
Я не могу решить проблему здесь
Чтобы проверить это, я изменил версию, чтобы передать тип версии с int
на char
. От компилятора нет жалоб, и номер версии правильный, так что мой код не сломан (1)
Проблема благотворительности
Поскольку предыдущая программа не содержит ошибок, я могу продолжить свою работу, давайте добавим простой char ET
в версии.Подождите, что случилось с моей версией?
Ответ прост: из-за разницы типов main читает int
из 4 байтов, но версия
определена как char, поэтому она имеет длину один байт. В предыдущей программе из-за удачи 3 дополнительных байта содержали нули, поэтому проблема не была обнаружена. Просто добавив новую переменную, мы изменили один из этих байтов и таким образом увеличили номер версии на 256.
Мы должны это исправить. Хорошая практика - скрыть реализацию с помощью вызова функции, давайте попробуем!
Половина передового опыта все еще остается хорошей практикой?
Хотя я провел рефакторинг с функцией, точно такой, как сказал этот поток stackoverflow, я все равно получаю номер версии, который не имеет смысла.
Ну, есть небольшая проблема: я забыл обновить main
, поэтому объявление extern все еще является int. Напечатанный здесь номер версии - это адрес функции version
.
Ради любопытства, сделаем ошибку с зеркалом. Здесь произошел сбой программы, потому что мы вызываем функцию по адресу 42. Поскольку 42 близок к нулю, он находится в недопустимом диапазоне памяти, и ОС вызвала ошибку сегментации. С другим значением он может вызвать допустимую функцию, возможно, что-то действительно не так.
Понимание проблемы
Используя механизм extern
, вы говорите компилятору: я объявил что-то, что определено в другом месте, вы найдете во время компоновки. Это проблематично для проверки типа, поскольку компоновщик работает просто с именами символов и адресами. Вся информация, относящаяся к типу, сейчас забыта.
Это можно проверить, посмотрев объектные файлы:
victor @ sogilis $ sh gcc -c main2.c
victor @ sogilis $ sh gcc -c version3.c
victor @ sogilis $ readelf -s main2.o version3.o
Файл: main2.o
Num: Value Size Type Bind Vis Ndx Name
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND версия
Файл: version3.o
Num: Value Size Type Bind Vis Ndx Name
7: 0000000000000000 1 ОБЪЕКТ ГЛОБАЛЬНОЕ ПО УМОЛЧАНИЮ 2 версия
Мы видим, что в main2.o
version
размер равен 0 и имеет НОТИП
.
Исправлением я бы назвал любой механизм, позволяющий обнаруживать несоответствие типов.Есть несколько способов сделать это.
Чистое исправление
Это исправление настолько очевидно, что я уверен, что это рефлекс почти каждого разработчика C. Если модуль определяет внешнюю переменную, то эта переменная является частью общедоступного интерфейса модуля и должна быть объявлена в заголовке.
Важное примечание: иногда вы можете встретить код C, в котором модуль не включает собственный заголовок. Как мы видим на этом примере, конфликт типов не обнаруживается.
Причина проста: определение C также является декларацией. Итак, version5.c
содержит два объявления: одно в заголовке и одно в файле C, а version6.c
скомпилировано без заголовка и есть одно объявление.
Исправление во враждебной среде
Если у вас есть кодовая база, уже поврежденная из-за неправильного использования extern, вы просто не можете исправить это в мгновение ока. Сначала вы хотите определить, есть ли типовые ошибки, которые вы еще не обнаружили с помощью своих тестов.
Это возможно благодаря опции -ftlo
(для оптимизатора времени компоновки). С помощью этой опции компилятор добавляет метаданные об объектах, а компоновщик использует их для выполнения нескольких оптимизаций. Поскольку метаданные содержат типы объектов, компоновщик также выдает предупреждение, если возникает путаница. Чтобы процитировать документацию:
, если LTO встречает объекты со связью C, объявленные с несовместимыми типами в отдельных единицах перевода, которые должны быть связаны вместе (неопределенное поведение в соответствии с ISO C99 6.2.7), может быть выдан нефатальный диагноз.
С помощью статического анализатора кода, такого как splint
, вы можете обнаружить проблему такого рода:
Без этого унаследованного проекта я бы никогда не исследовал так далеко, какой ущерб может быть нанесен в этой ситуации. Это напоминает нам, разработчикам C, какой опасный язык мы используем, и насколько мы особенные и совершенные, чтобы заставить его работать.
Оставим последнее слово за специалистом:
Христос, люди.Изучите C вместо того, чтобы просто строить случайные символы вместе, пока он не скомпилируется (с предупреждениями). Линус Торвальдс
Виктор Ламбре
Особая благодарность Graham & Haze за их отзывы
(1) для целей этой статьи, давайте представим, что я , что наивный
.