Подтвердить что ты не робот

Почему инициализация переменной 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. Экстерн с инициализатором является определением. Ошибочно предоставлять инициализатор во внешней части функции.

Может ли кто-нибудь дать объяснение, почему это ошибка, если она выполняется локально в функции, хотя это допускается в глобальной области?

4b9b3361

Ответ 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.