Почему инициализация переменной extern внутри функции дает ошибку?
Этот код прекрасно компилируется:
extern int i = 10;
void test()
{
std::cout << "Hi" << i << std::endl;
}
Пока этот код выдает ошибку:
void test()
{
extern int i = 10;
std::cout << "Hi" << i << std::endl;
}
ошибка: у 'i' есть 'extern' и инициализатор
Я прочитал это в C++ Primer:
Любое объявление, которое включает явный инициализатор, является определением. Мы можем предоставить инициализатор для переменной, определенной как extern, но это переопределяет extern. Экстерн с инициализатором является определением. Ошибочно предоставлять инициализатор во внешней части функции.
Может ли кто-нибудь дать объяснение, почему это ошибка, если она выполняется локально в функции, хотя это допускается в глобальной области?
Ответы
Ответ 1
Причина, определяющая внешнюю переменную внутри функции, не имеет смысла:
Когда вы объявляете символ extern, вы говорите компилятору, чтобы связать все такие вхождения этого значения в один и тот же символ. Любые случаи extern int i; в вашей программе будет ссылка на внешний i. Посмотрите на этот пример:
#include <iostream>
using namespace std;
extern int i;
int i = 10;
void test()
{
std::cout << "Hi" << i << std::endl;
}
int main()
{
extern int i;
i++;
test();
}
В этом примере должен выводиться hi11. Но если мы удалим extern внутри main, он выведет 10. Это потому, что без extern я не привязываюсь к глобальному i, но создаю его собственную локальную копию i.
Причина того, что определение extern я внутри функции не имеет смысла, - это то, что если мы разрешили какой-либо функции "определять" i. Какая функция работает в первую очередь? Когда он определяется?
Предположим, что приведенный ниже пример действителен, каков будет выход:
#include <iostream>
using namespace std;
extern int i;
int i = 10;
void test()
{
std::cout << "Hi" << i << std::endl;
}
void test2() {
extern int i = 1000;
std::cout<< "HI" << i << std::endl;
}
void test3() {
extern int i;
i = 1000;
std::cout<< "HI" << i << std::endl;
}
int main()
{
extern int i;
i++;
test();
i = 0;
test2();
}
Если результат теста2 равен 0 или 1000? Также посмотрите на мой test3, здесь мы кратко говорим, что свяжите мой я с внешним я и присвойте ему значение 1000. Это сильно отличается от попытки инициализировать значение.
Короче говоря, внешние переменные действительно имеют смысл только как глобальные и должны быть определены в глобальном масштабе. В ваших примерах первая версия не компилируется ни для меня. Я нахожу это интересным. Возможно, стоит посмотреть на документацию по стандартам, чтобы понять, насколько это определено в сжатом виде, или если ваш компилятор может справиться с этим способом, предназначенным для добавления дополнительной защиты...
Ответ 2
Добавляя инициализатор к объявлению, он становится определением глобальной переменной. Это эквивалентно тому же определению без extern
, что означает ваша книга, когда он говорит, что "переопределяет экстерн".
В то время как глобальные переменные могут быть объявлены (используя extern
) внутри функции, они не могут быть определены там, только в области пространства имен. Вот почему второй фрагмент является ошибкой.
Если вы хотите знать, почему дизайнеры C (откуда эти правила пришли на С++) решили разрешить объявления, но не определения здесь, то, боюсь, я не знаю историю языков достаточно подробно, чтобы ответить.
Ответ 3
Сначала вы должны знать концепцию связи и смысл внешней связи:
Говорят, что имя имеет связь, когда он может обозначать один и тот же объект, ссылка, функция, тип, шаблон, пространство имен или значение в качестве имени введенные декларацией в другой области:
Если имя имеет внешнюю связь, то объект, который он обозначает, может быть на которые ссылаются имена из областей других единиц перевода или из другие области той же единицы перевода.
--3.5.6.2 n3242
Функция static
, которая отличается от extern
, extern
, является просто запросом, static
является командой.
Имя функции, объявленной в области блока, и имя переменная, объявленная объявлением extext блока, имеет связь.
- Если есть видимое объявление объекта с привязкой, имеющим одно и то же имя и тип, игнорируя объекты, объявленные вне самой внутренней охватывающей области пространства имен, объявление области кадра объявляет тот же объект и получает связь с предыдущей декларацией.
- Если существует более одного такого совпадающего объекта, программа плохо сформирована.
- В противном случае, если соответствующий объект не найден, объект области блока получает внешнюю привязку.
- 3.5.6.6 n3242
Поэтому в области блока рекомендуется выполнить следующую процедуру:
extern int i;//declare it,request the linkage according to 3.5.6.6 above
i = 10;//modify it when has link to a defination
Для глобального объявления extern возможно преобразовать форму
extern int i =10;
к
extern int i;//include in .hpp is recommended
int i =10;//global or namespace variable defination
Ответ 4
Самый простой способ:
Цель ключевого слова extern
- объявить объект без его определения. Определив его, вы в основном говорите компилятору "Не присваивайте значение, а присваивайте значение". Это не имеет смысла - это никогда не должно быть сделано, внутри или вне функции. Большинство компиляторов либо предупредит вас, либо все равно продолжит или вообще не будет компилятором и сообщит об ошибке.
Хотя это выходит за рамки этого вопроса, чтобы подробно объяснить, что делает extern
, вам может показаться полезным прочитать ответы на этот вопрос.
Ответ 5
extern
переменные инициализируются до запуска любой функции:
en.cppreference.com/w/cpp/language/initialization#Non-local_variables
Если бы он был объявлен static
, а не extern
внутри функционального блока, он все равно имел бы статическую продолжительность хранения, но его "привязка" была бы локальной для этой функции и внешней. Таким образом, он будет инициализирован, когда выполнение сначала пройдет через эту строку внутри функции:
en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables
Так что нормально инициализировать переменные static
в функциональном блоке, но не нормально инициализировать там переменные extern
.