Extern c что это

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

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

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

В двух словах

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

  • Non-const глобальные переменные — внешнее связывание;
  • Const глобальные переменные — внутреннее связывание;
  • Функции — внешнее связывание.

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

  • Разница между объявлением и определением;
  • Единицы трансляции.

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

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

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

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

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

Не только объявляет x , но и определяет его. Происходит это благодаря вызову дефолтного конструктора int. (В C++ в отличие от Java, конструктор простых типов (таких как int) по умолчанию не инициализирует значение в 0. В примере выше х будет иметь равен любому мусору, лежащему в адресе памяти, выделенном компилятором).

Но вы можете явно разделить объявление переменной и ее определение при помощи ключевого слова extern .

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

Предварительное Объявление

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

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

Сразу включить определение Class — наивно. Но так как мы пока только объявили f , достаточно предоставить компилятору объявление Class . Таким образом, компилятор сможет узнать функцию по ее прототипу, а мы сможем избавиться от зависимости file.hpp от файла, содержащего определение Class , скажем class.hpp:

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

Частота использования

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

А это не работает:

Единицы трансляции

Программисты обычно работают с заголовочными файлами и файлами реализации. Но не компиляторы — они работают с единицами трансляции (translation units, кратко — TU), которые иногда называют единицами компиляции. Определение такой единицы довольно простое — любой файл, переданный компилятору, после его предварительной обработки. Если быть точным, это файл, получаемый в результате работы препроцессора расширяющего макрос, включающего исходный код, который зависит от #ifdef и #ifndef выражений, и копипасты всех файлов #include .

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

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

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

Внешняя связь

Когда символ (переменная или функция) обладает внешней связью, он становится видимым компоновщикам из других файлов, то есть “глобально” видимым, доступным всем единицами трансляции. Это значит, что вы должны определить такой символ в конкретном месте одной единицы трансляции, обычно в файле реализации (.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. Компоновщик замирает от счастья и говорит вам примерно следующее:

Компоновщик находит два определения для одного символа f . Поскольку у f есть внешнее связывание, он виден компоновщику при обработке и A, и B. Очевидно, это нарушает Правило Одного Определения и вызывает ошибку. Точнее это вызывает ошибку повторяющегося символа (duplicate symbol error), которую вы будете получать не реже, чем ошибку неопределенного символа (undefined symbol error), возникающую, когда вы объявили символ, но забыли определить.

Использование

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

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

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

Внутренняя Связь

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

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

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

  1. file1.cpp
  2. file2.cpp
  3. main.cpp

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

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

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

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

Использование

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

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

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

Что означает extern "C" или extern "C++"?

Декларирование C++ кода в extern "C"
Как правильно декларировать C++ код в extern &quot;C&quot; ? чтобы подтянуть его через cpython.

Разница только в том, с каким именем создастся метка в коде

Evg,
Почти ничего не понял
Какая разница как функция называется в ассемблере? Как это мешает выполняться коду который соответствует этой функции?

Всмысле написать программу исходники которой писаны на си и с++? Компилятор же при компиляции одновременно не использует два стандарта. Он либо компилит код по определённому стандарту си, либо с++(или не так?).

Сообщение от Undisputed

Croessmah,
То есть речь уже о скомпилированной библиотеки которая подключается в коде? (Прагмой вроде).

Т.е насколько я понял если мне хочется использовать функцию из сишной либы, я пишу extern "C" имя функции, далее плюсовый компилятор преобразует это имя функции в то как если бы мы компилировали сишный а не плюсовый код. Таким образом получится вызвать функцию из заранее скомпилированной сишный либы. И это весь смысл extern "C"?

Сообщение от Undisputed

nd2,
Вы меня ещё больше запутали если extern "C" не преобразует название функции в си-стиль, тогда что оно делает и зачем нужно?

7.7. Директива связывания extern "C" A

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

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

// директива связывания в форме простой инструкции

extern «C» void exit(int);

// директива связывания в форме составной инструкции

int printf( const char* . );

int scanf( const char* . );

// директива связывания в форме составной инструкции

Первая форма такой директивы состоит из ключевого слова extern, за которым следует строковый литерал, а за ним – “обычное” объявление функции. Хотя функция написана на другом языке, проверка типов вызова выполняется полностью. Несколько объявлений функций могут быть помещены в фигурные скобки составной инструкции директивы связывания – второй формы этой директивы. Скобки отмечают те объявления, к которым она относится, не ограничивая их видимости, как в случае обычной составной инструкции. Составная инструкция extern «C» в предыдущем примере говорит только о том, что функции printf() и scanf() написаны на языке С. Во всех остальных отношениях эти объявления работают точно так же, как если бы они были расположены вне инструкции.

Если в фигурные скобки составной директивы связывания помещается директива препроцессора #include, все объявленные во включаемом заголовочном файле функции рассматриваются как написанные на языке, указанном в этой директиве. В предыдущем примере все функции из заголовочного файла cmath написаны на языке С.

Директива связывания не может появиться внутри тела функции. Следующий фрагмент кода вызывает ошибку компиляции:

// ошибка: директива связывания не может появиться

// внутри тела функции

extern «C» double sqrt( double );

double getValue(); //правильно

double result = sqrt ( getValue() );

Если мы переместим директиву так, чтобы она оказалась вне тела main(), программа откомпилируется правильно:

extern «C» double sqrt( double );

double getValue(); //правильно

double result = sqrt ( getValue() );

Однако более подходящее место для директивы связывания – заголовочный файл, где находится объявление функции, описывающее ее интерфейс.

Как сделать С++ функцию доступной для программы на С? Директива extern «C» поможет и в этом:

// функция calc() может быть вызвана из программы на C

extern «C» double calc( double dparm ) < /* . */ >

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

extern «C» double calc( double );

// объявление calc() в myMath.h

// определение функции extern «C» calc()

// функция calc() может быть вызвана из программы на C

double calc( double dparm ) < // . >

В данном разделе мы видели примеры директивы связывания extern «C» только для языка С. Это единственный внешний язык, поддержку которого гарантирует стандарт С++. Конкретная реализация может поддерживать связь и с другими языками. Например, extern «Ada» для функций, написанных на языке Ada; extern «FORTRAN» для языка FORTRAN и т.д. Мы описали один из случаев использования ключевого слова extern в С++. В разделе 8.2 мы покажем, что это слово имеет и другое назначение в объявлениях функций и объектов.

exit(), printf(), malloc(), strcpy() и strlen() являются функциями из библиотеки С. Модифицируйте приведенную ниже С-программу так, чтобы она компилировалась и связывалась в С++.

const char *str = «hello»;

void *malloc( int );

char *strcpy( char *, const char * );

int printf( const char *, . );

int strlen( const char * );

char* s = malloc( strlen(str)+l );

printf( «%s, world «, s );

Читайте также

R.16.9 Пустая директива

R.16.9 Пустая директива Команда препроцессора вида#не оказывает никакого

Программный способ связывания данных

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

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

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

Директива .maxstack

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

КЛЮЧЕВЫЕ СЛОВА: auto, extern, static, register

КЛЮЧЕВЫЕ СЛОВА: auto, extern, static, register Одно из достоинств языка Си состоит в том, что он позволяет управлять ключевыми механизмами программы. Классы памяти языка Си — пример такого управления; они дают возможность определить, с какими функциями связаны какие переменные и

Разрешение конфликтов посредством связывания

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

Преимущества и недостатки связывания

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

Директива #define

Директива #define Синтаксис:#define &lt;идентификатор&gt; &lt;текст&gt;#define &lt;идентификатор&gt; &lt;список параметров&gt; &lt;текст&gt;Директива #define заменяет все вхождения &lt;идентификатора&gt; в исходном файле на &lt;текст&gt;, следующий в директиве за &lt;идентификатором&gt;. Этот процесс

Директива #undef

Директива #undef Синтаксис:#undef &lt;идентификатор&gt;Директива #undef отменяет действие текущего определения #define для &lt;идентификатора&gt;. Чтобы отменить макроопределение посредством директивы #undef, достаточно задать его &lt;идентификатор&gt;. Задание списка параметров не

Директива обработки ошибок

Директива обработки ошибок В СП ТС реализована директива #error. Ее формат:#error &lt;текст&gt;Обычно эту директиву записывают среди директив условной компиляции для обнаружения некоторой недопустимой ситуации. По директиве #error препроцессор прерывает компиляцию и выдает

Пустая директива

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

3.12. Директива typedef

3.12. Директива typedef Директива typedef позволяет задать синоним для встроенного либо пользовательского типа данных. Например:typedef double wages;typedef vectorint vec_int;typedef vec_int test_scores;typedef bool in_attendance;typedef int *Pint;Имена, определенные с помощью директивы typedef, можно использовать точно так же, как

7.9.6. Указатели на функции, объявленные как extern "C"

7.9.6. Указатели на функции, объявленные как extern "C" Можно объявлять указатели на функции, написанные на других языках программирования. Это делается с помощью директивы связывания. Например, указатель pf ссылается на С-функцию:extern "C" void (*pf)(int);Через pf вызывается функция,

9.1.5. Директива extern "C" и перегруженные функции A

9.1.5. Директива extern "C" и перегруженные функции A В разделе 7.7 мы видели, что директиву связывания extern "C" можно использовать в программе на C++ для того, чтобы указать, что некоторый объект находится в части, написанной на языке C. Как эта директива влияет на объявления

О реализации динамического связывания

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

Extern в C++

C++

Программирование и разработка

Слово «extern» в C ++ является спецификатором. Его использование в C ++ объясняется в этой статье для переменных и функций. Сначала дается значение объявления и определения в C ++. Рассмотрим следующие строки кода:

int it ;

char fn ( int itg, char ch ) ;

it = 5 ;

char fn ( int itg, char ch ) <
char var = ‘o’ ;
if ( itg == 1 && ch == ‘a’ )
var = ‘z’ ;
return var ;
>

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

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

Подобное полное объявление переменной, в котором переменная вводится, а затем ей присваивается значение, остается объявлением переменной. Итак, объявление переменной может просто вводить переменную или вводить ее вместе с определением.

Следующий код (скопированный сверху) является объявлением функции:

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

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

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

Заголовочный файл без внешнего

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

Где head.hh — это имя файла заголовка, который находится в домашнем каталоге пользователя. Директива include не заканчивается точкой с запятой. В этой ситуации объявлениям переменных без определений и прототипам функций без определений функций в файле заголовка не должен предшествовать спецификатор extern. И приложение должно работать.

Illustration

Указанные выше переменная и функция используются здесь для иллюстрации.

Введите следующий код в текстовом редакторе и сохраните его в домашнем каталоге пользователя с именем head.hh:

int it = 5 ;
char fn ( int itg, char ch ) ;

В заголовке всего два утверждения. Затем введите следующее в безымянный документ текстового редактора и сохраните в домашнем каталоге пользователя с именем second.cpp:

Затем введите следующий код в другой безымянный документ текстового редактора и сохраните его в домашнем каталоге пользователя с именем first.CPP:

Скомпилируйте приложение с помощью следующей команды терминала:

Запустите приложение с помощью,

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

extern без файла заголовка

Заголовочный файл можно удалить, если правильно использовать спецификатор extern. В этой ситуации будет объявление для переменной и функции, каждое без определения в первом (основном) файле. Каждому будет предшествовать extern.

Illustration

Введите следующий код в текстовом редакторе и сохраните его в домашнем каталоге пользователя с именем first.cpp:

#include
using namespace std ;

extern int it ;

extern char fn ( int itg, char ch ) ;

int main ( )
<
cout << it << endl ;
cout << fn ( 1 , ‘a’ ) << endl ;

return ;
>

Затем введите следующее в безымянный документ текстового редактора и сохраните в домашнем каталоге пользователя с именем second.cpp:

int it = 5 ;

char fn ( int itg, char ch ) <
char var = ’о’ ;
если ( itg == 1 && ch == ’a’ )
var = ’z’ ;
return var ;
>

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

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

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