Что такое extern c

extern

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

Таблица. Использование глобальных переменных в раздельно компилируемых файлах

Файл 1Файл 2
int х, у;
char ch;

void func1 (void)
<
x = 23;
>

extern int x, y;
extern char ch;

void func23(void)
<
y=10;
>

В файле 2 список глобальных переменных копируется из файла 1 и при объявлении добавляется спецификатор extern. Спецификатор extern сообщает компилятору, что следующие за ним типы и имена переменных объявляются где-то в другом месте. Другими словами, extern позволяет компилятору знать о типах и именах глобальных переменных без действительного создания этих переменных. Когда два модуля объединяются, все ссылки на внешние переменные пересматриваются.

Если при объявлении выделяется память под переменную, то процесс называется определением. Использование extern приводит к объявлению, но не к определению. Оно просто говорит компилятору, что определение происходит где-то в другом месте программы.

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

int first, last; /*глобальное определение first и last */
int main (void)
<
extern int first; /* необязательное использование extern объявления */
.
>

Хотя объявление переменной с extern может иметь место в одном файле с объявлением глобальной переменной, в этом на самом деле нет необходимости. Если компилятор С встречает переменную, которая не была объявлена, то компилятор проверяет, соответствует ли она какой-либо глобальной переменной. Если это так, то компилятор предполагает, что эта переменная ссылается на глобальную.

Источник

extern (C++)

The extern keyword may be applied to a global variable, function, or template declaration. It specifies that the symbol has external linkage. For background information on linkage and why the use of global variables is discouraged, see Translation units and linkage.

The extern keyword has four meanings depending on the context:

In a non- const global variable declaration, extern specifies that the variable or function is defined in another translation unit. The extern must be applied in all files except the one where the variable is defined.

In a const variable declaration, it specifies that the variable has external linkage. The extern must be applied to all declarations in all files. (Global const variables have internal linkage by default.)

extern «C» specifies that the function is defined elsewhere and uses the C-language calling convention. The extern «C» modifier may also be applied to multiple function declarations in a block.

extern linkage for non- const globals

When the linker sees extern before a global variable declaration, it looks for the definition in another translation unit. Declarations of non- const variables at global scope are external by default. Only apply extern to the declarations that don’t provide the definition.

extern linkage for const globals

A const global variable has internal linkage by default. If you want the variable to have external linkage, apply the extern keyword to the definition, and to all other declarations in other files:

extern constexpr linkage

extern «C» and extern «C++» function declarations

In C++, when used with a string, extern specifies that the linkage conventions of another language are being used for the declarator(s). C functions and data can be accessed only if they’re previously declared as having C linkage. However, they must be defined in a separately compiled translation unit.

Microsoft C++ supports the strings «C» and «C++» in the string-literal field. All of the standard include files use the extern «C» syntax to allow the run-time library functions to be used in C++ programs.

Example

The following example shows how to declare names that have C linkage:

If a function has more than one linkage specification, they must agree. It’s an error to declare functions as having both C and C++ linkage. Furthermore, if two declarations for a function occur in a program, one with a linkage specification and one without, the declaration with the linkage specification must be first. Any redundant declarations of functions that already have linkage specification are given the linkage specified in the first declaration. For example:

Источник

extern (C++)

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

extern Ключевое слово имеет четыре значения в зависимости от контекста:

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

В const объявлении переменной указывается, что переменная имеет extern связь Al. extern Должен применяться ко всем объявлениям во всех файлах. ( const По умолчанию глобальные переменные имеют внутреннюю компоновку.)

extern «C» Указывает, что функция определена в любом расположении и использует соглашение о вызовах на языке C. extern «C» Модификатор может также применяться к нескольким объявлениям функций в блоке.

extern компоновка для const неглобальных элементов

Если компоновщик видит extern перед объявлением глобальной переменной, он ищет определение в другой записи преобразования. Объявления const непеременных в глобальной области extern по умолчанию имеют значение Al. Применяется только extern к объявлениям, не предоставляющим определение.

extern компоновка для const глобальных элементов

const По умолчанию глобальная переменная имеет внутреннюю компоновку. Если требуется, чтобы переменная соимела extern связь Al, примените extern к определению ключевое слово и ко всем остальным объявлениям в других файлах:

extern constexpr компоновка

extern «C» и extern «C++»

В C++ при использовании со строкой extern указывает, что для деклараторов используются соглашения компоновки другого языка. Доступ к функциям и данным C можно получить только в том случае, если они были ранее объявлены с компоновкой C. Однако они должны быть определены в блоке трансляции, который компилируется отдельно.

Пример

В следующем примере показано, как объявить имена с компоновкой C:

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

Источник

Что такое extern c

Вы наверняка знаете, что ключевое слово extern применяют для того, чтобы совместно использовать одну и ту же переменную в разных модулях кода на языке C/C++. С помощью extern переменные становятся глобальными. Но что в реальности происходит, когда используется extern? Что такое декларация? Какая область действия у переменной? Как правильно использовать extern?

Использование extern уместно только в тех случаях, когда построенная Вами программа состоит из нескольких исходных файлов, соединяемых вместе на этапе линковки, где некоторые переменные определены, например, в исходном файле file1.c, и к ним нужно обращаться в других исходных файлах, таких как file2.c.

Важно понимать разницу между терминами «определение переменной» (defining a variable) и «декларирование переменной» (иногда говорят «объявление переменной», declaring a variable). Причем можно определять и декларировать не только переменные, но и константы. Вот смысл этих понятий:

Переменная (или константа) определена в том месте программы, где компилятор выделяет под неё память. Переменная (или константа) декларируется, когда компилятор информируется о том, что эта переменная где-то уже определена.

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

[Как лучше всего декларировать и определять переменные]

file3.h

file1.c

file2.c

Подобным образом можно декларировать и определять функции. Пример (функции increment и use_it):

prog1.h

prog1.c

Модуль prog1 использует prog1.c, file1.c, file2.c, file3.h и prog1.h.

Примечание: по каким-то странным обстоятельствам, известным только Брайану Кернигану и Деннису Ритчи, при декларации функций использовать ключевое слово extern не обязательно. Это не относится к декларации переменных. Т. е., примеру вот эти две декларации функции совершенно равнозначны.

И так тоже правильно:

[Общие правила]

• Заголовочный файл (header, файл с расширением *.h) должен содержать в себе только extern-декларации переменных. Здесь не должно быть никаких статических переменных (static).

• Для любой имеющейся переменной только один заголовочный файл должен декларировать её (правило SPOT — Single Point of Truth, т. е. «правда только в одном месте»).

• Никогда не вставляйте в модуль исходного кода (файл с расширением *.c или *.cpp) extern-декларации переменных — исходные файлы всегда подключают (единственный) хедер, в котором декларируются переменные.

• В функции никогда не следует декларировать переменную с использованием extern.

Если Вы не являетесь опытным C-программистом, то можете (и возможно должны) дальше не читать.

[Не очень хороший способ определять глобальные переменные]

С некоторыми компиляторами языка C (в действительности, со многими) Вам может сойти с рук то, что можно назвать также «общим» определением переменной. Слово «общий» здесь относится к технике, используемой в языке Fortran для совместного использования переменной между исходными файлами, используя (возможно именованный) ОБЩИЙ блок кода. Подобное произойдет, когда каждый из нескольких файлов предоставят предварительное определение переменной. Пока не больше, чем в одном файле есть инициализированное определение, различные файлы используют одно общее определение переменной:

file10.c

file11.c

file12.c

Эта техника не придерживается букве стандарта C и правилу ‘одного определения’, однако стандарт C упоминает это как общую вариацию этого правила одного определения. Поскольку эта техника поддерживается не всегда, то лучше её избегать, особенно если Ваш код должен в будущем портироваться на другие платформы и системы. С использованием этой техники Вы можете также столкнуться с неожиданным каламбуром типов. Если в одном из файлов переменная i декларирована как double вместо int, то линковщики, не соблюдающие жесткую типизацию C, возможно не определят несоответствие типов. Если на Вашем компьютере у типов int и double разрядность 64 бита, то даже не получите предупреждения; но на компьютере, где int 32-битный, и double 64-битный, скорее всего линковщик выдаст предупреждение о разных размерах типов, и линковщик будет использовать самый большой размер, точно так же как программа на Фортране будет использовать самый большой размер переменной из любого общего блока.

Это упомянуто в C-стандарте Annex J как общее расширение:

Перевод: может существовать больше одного внешнего определения для идентификатора объекта, вместе с явным использованием ключевого слова extern или без его использования; если эти определения противоречат друг другу, или если имеется больше одной инициализации в определениях, то поведение кода не определено (6.9.2).

В следующих 2 файлах приведен полный код для prog2:

prog2.h

prog2.c

Модуль программы prog2 использует prog2.c, file10.c, file11.c, file12.c, prog2.h.

Предупреждение: использование нескольких конкурентных определений для глобальной переменной приводит к неопределенному поведению, которое является способом для стандарта выразить, что «что-то, но непонятно что, должно произойти». Т. е. может произойти так, что в одном случае программа будет вести себя так, как ожидалось; как сказано в J.5.11, приблизительно «Вы могли бы быть удачливы чаще, чем того заслуживаете». Но программа, которая полагается не несколько определений внешней переменной — с ключевым словом ‘extern’ или без него — не только запутывает программиста, но и еще не гарантирует, что будет работать везде. Короче говоря: код будет содержать ошибку, которая может не показать себя.

[Нарушение правил]

faulty_header.h

Замечание 1: если в хедере определена переменная без ключевого слова extern, то каждый файл, который подключит этот заголовок, создаст предварительное определение переменной.

broken_header.h

Замечание 2: если заголовок определяет и инициализирует переменную, то только один исходный файл в программе может использовать этот заголовок. Смысл заголовочного файла теряется!

seldom_correct.h

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

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

[Общие выводы]

Всегда используйте стандартную технику заголовков (как это описано в разделе «Общие правила»). Это надежно работает везде. Обратите внимание, в частности, что заголовок, где декларируется внешняя переменная, следует подключить во все файлы, которые используют эту переменную, даже в тот файл, где переменная определена. Это гарантирует точное соответствие декларации и определения переменной.

Подобные проблемы возникают и с декларированием и определением функций — для этого работают аналогичные правила. К константам относится то же самое.

При соблюдении описанных правил получается так, что в код определения функций подставляются и их декларации. Я использую в заголовках как для переменных, так и для функций при декларации ключевое слово extern. Однако многие программисты не используют extern перед функциями при их декларации; компилятор это не беспокоит, так что не указание extern перед декларации функции не составит проблему, пока Вы последовательны в кодировании.

[Избегайте дублирования кода]

Другая проблема может возникнуть с тем, что переменные должны быть определены в каждой из нескольких «основных программ, main». Обычное побочное беспокойство; Вы можете просто представить исходный файл C для определения переменных и прилинковать его объектный файл к каждой из программ.

Обычная схема может выглядеть следующим образом, с использованием глобальной переменной, которая иллюстрировалась в файле file3.h:

Источник

Внутренняя и внешняя линковка в C++

Представляем вам перевод интересной статьи, который подготовили для вас рамках курса «Разработчик C++». Надеемся, что она будет полезна и интересна для вас, как и нашим слушателям.

Сталкивались ли вы когда-нибудь с терминами внутренняя и внешняя связь? Хотите узнать, для чего используется ключевое слово extern, или как объявление чего-то static влияет на глобальную область? Тогда эта статья для вас.

В единицу трансляции включены файл реализации (.c/.cpp) и все его заголовочные файлы (.h/.hpp). Если внутри единицы трансляции у объекта или функции есть внутреннее связывание, то этот символ виден компоновщику только внутри этой единицы трансляции. Если же у объекта или функции есть внешнее связывание, то компоновщик сможет видеть его при обработке других единиц трансляции. Использование ключевого слова static в глобальном пространстве имен дает символу внутреннее связывание. Ключевое слово extern дает внешнее связывание.
Компилятор по умолчанию дает символам следующие связывания:

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

Объявление VS. Определение

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

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

Разница между определением и объявлением функции весьма очевидна.

С переменными все немного иначе. Объявление и определение обычно не разделяются. Главное, что это:

Однако, при инициализации и добавлении extern к объявлению, выражение превращается в определение и ключевое слово extern становится бесполезным.

В C++ существует концепция предварительного объявления символа. Это значит, что мы объявляем тип и имя символа для использования в ситуациях, не требующих его определения. Так нам не понадобится включать полное определение символа (обычно — заголовочный файл) без явной необходимости. Тем самым, мы снижаем зависимость от файла, содержащего определение. Главное преимущество — при изменении файла с определением, файл, где мы предварительно объявляем этот символ, не потребует повторной компиляции (а значит, и все прочие файлы его включающие).

Предположим, у нас есть объявление функции (называемое прототипом) для f, принимающее объект типа Class по значению:

Допустим, file.hpp содержится в 100 других файлах. И, допустим, мы меняем определение Class в class.hpp. Если вы добавим class.hpp в file.hpp, file.hpp и все 100 содержащих его файла будут должны перекомпилироваться. Благодаря предварительному объявления Class единственными файлами, требующими повторной компиляции, будут class.hpp и file.hpp (если считать, что f определен там).

Важное отличие объявления от определения состоит в том, что символ может быть объявлен много раз, но определен только однажды. Так вы можете предварительно объявить функцию или класс сколько угодно раз, но определение может быть только одно. Это называется Правилом Одного Определения. В C++ работает следующее:

Есть следующие файлы:

Препроцессор выдаст следующую единицу трансляции, которая затем передается компилятору:

Обсудив основы, можно приступить к связям. В целом, связь — это видимость символов для компоновщика при обработке файлов. Связь может быть либо внешней, либо внутренней.

Когда символ (переменная или функция) обладает внешней связью, он становится видимым компоновщикам из других файлов, то есть “глобально” видимым, доступным всем единицами трансляции. Это значит, что вы должны определить такой символ в конкретном месте одной единицы трансляции, обычно в файле реализации (.c/.cpp), так чтобы у него было только одно видимое определение. Если вы попытаетесь одновременно с объявлением символа выполнить его определение, или поместить определение в файл к объявлению, то рискуете разозлить компоновщик. Попытка добавить файл больше чем в один файл реализации, ведет к добавлению определения больше чем в одну единицу трансляции — ваш компоновщик будет плакать.

Ключевое слово extern в C и C++ (явно) объявляет, что у символа есть внешняя связь.

Оба символа имеют внешнюю связь. Выше отмечалось, что const глобальные переменные по умолчанию имеют внутреннее связывание, non-const глобальные переменные — внешнее. Это значит, что int x; — то же самое, что и extern int x;, верно? Не совсем. int x; на самом деле аналогичен extern int x<>; (используя синтаксис универсальной/скобочной инициализации, для избежания самого неприятного синтаксического анализа (the most vexing parse)), так как int x; не только объявляет, но и определяет x. Следовательно, не добавить extern к int x; глобально настолько же плохо, как определить переменную при объявлении ее extern:

Давайте объявим функцию f с внешней связью в file.hpp и там же определим ее:

Обратите внимание, что добавлять здесь extern не нужно, так как все функции явно extern. Разделения объявления и определения тоже не потребуется. Поэтому давайте просто перепишем это следующим образом:

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

Давайте посмотрим, почему так делать не стоит. Теперь у нас есть два файла реализации: a.cpp и b.cpp, оба включены в file.hpp:

Теперь пусть поработает компилятор и сгенерирует две единицы трансляции для двух файлов реализации выше (помните что #include буквально означает копировать/вставить):

На этом этапе вмешивается компоновщик (связывание происходит после компиляции). Компоновщик берет символ f и ищет определение. Сегодня ему повезло, он находит аж два! Одно в единице трансляции A, другое в B. Компоновщик замирает от счастья и говорит вам примерно следующее:

Стандартным примером объявления переменных extern являются глобальные переменные. Предположим, вы работаете над самовыпекаемым тортом. Наверняка есть глобальные переменные, связанные с тортом, которые должны быть доступны в разных частях вашей программы. Допустим, тактовая частота съедобной схемы внутри вашего торта. Это значение естественно требуется в разных частях для синхронной работы всей шоколадной электроники. (Злой) C-способ объявления такой глобальной переменной имеет вид макроса:

Программист C++, испытывающий к макросам отвращение, лучше напишет настоящий код. Например такой:

(Современный программист C++ захочет использовать разделительные литералы: unsigned int clock_rate = 1’000’000;)

Если у символа есть внутренняя связь, то он будет виден только внутри текущей единицы трансляции. Не путайте видимость с правами доступа, например private. Видимость означает, что компоновщик сможет использовать этот символ только при обработке единицы трансляции, в которой был объявлен символ, а не позже (как в случае символов с внешней связью). На практике, это значит, что при объявлении символа с внутренней связью в заголовочном файле, каждая единица трансляции, включающая в себя этот файл, получит уникальную копию этого символа. Как если бы вы предопределили каждый такой символ в каждой единице трансляции. Для объектов это значит, что компилятор будет буквально выделять совершенно новую, уникальную копию для каждой единицы трансляции, что, очевидно, может привести к высоким расходам памяти.

Для объявления символа с внутренней связью, в C и C++ существует ключевое слово static. Такое использование отличается от применения static в классах и функциях (или, в целом, в любых блоках).

Каждая единица трансляции, включающая header.hpp получает уникальную копию переменной, в силу наличия у нее внутренней связи. Есть три единицы трансляции:

Анонимные пространства имен

В С++ существует другой способ объявления одного и более символов с внутренней связью: анонимные пространства имен. Такое пространство гарантирует, что символы, объявленные внутри него, видны только в текущей единице трансляции. По сути, это просто способ объявить несколько символов static. Какое-то время от использования ключевого слова static в целях объявления символа с внутренней связью отказались в пользу анонимных пространств имен. Однако, им снова стали пользоваться в силу удобства объявления одной переменной или функции с внутренней связью. Есть еще несколько незначительных отличий, на которых я не буду останавливаться.

В любом случае, это:

Делает (почти) то же самое, что и:

Так в каких же случаях пользоваться внутренними связями? Использовать их для объектов — плохая идея. Расход памяти больших объектов может быть очень высок из-за копирования под каждую единицу трансляции. Но, в основном, это просто вызывает странное, непредсказуемое поведение. Представьте, что у вас есть синглтон (класс, в котором вы создаете экземпляр только одного инстанса) и неожиданно появляется несколько инстансов вашего “синглтона” (по одному на каждую единицу трансляции).

Однако, внутреннюю связь можно использовать для скрытия из глобальной области локальных хелпер-функций единицы трансляции. Допустим, есть хелпер-функция foo в file1.hpp, которую вы используете в file1.cpp. В то же время у вас есть функция foo в file2.hpp, используемая в file2.cpp. Первая и вторая foo отличаются друг от друга, но вы не можете придумать другие имена. Поэтому вы можете объявить их static. Если вы не будете добавлять и file1.hpp, и file2.hpp в одну и ту же единицу трансляции, то это скроет foo друг от друга. Если этого не сделать, то они будут неявно иметь внешнюю связь и определение первой foo столкнется с определением второй, вызвав ошибку компоновщика о нарушении правила одного определения.

Вы всегда можете оставить свои комментарии и\или вопросы тут или зайти к нам на день открытых дверей.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *