Как можно использовать переменную, когда ее определение обойдено? - PullRequest
36 голосов
/ 16 декабря 2011

На мой взгляд, определение всегда означает распределение памяти.

В следующем коде int i выделяет 4-байтовое (обычно) хранилище в программном стеке и связывает его с i, а i = 3 назначает 3 этому хранилищу. Но из-за goto определение обойдено, что означает, что для i.

нет выделенной памяти.

Я слышал, что локальные переменные размещаются либо в начале функции (в данном случае f()), где они находятся, либо в точке определения.

Но в любом случае, как можно использовать i, пока он еще не определен (вообще нет хранилища)? Где значение три присваивается при выполнении i = 3?

void f()
{
    goto label;
    int i;

label:
    i = 3;
    cout << i << endl; //prints 3 successfully
}

Ответы [ 8 ]

44 голосов
/ 16 декабря 2011

Короче говоря; goto приведет к переходу во время выполнения, определение / объявление переменной приведет к выделению памяти, времени компиляции.

Компилятор увидит и определит, сколько памяти выделить для int, он также сделает так, чтобы это выделенное хранилище было установлено на 3 при "нажатии" i = 3;.

Это место в памяти будет там, даже если в начале вашей функции есть goto перед объявлением / определением, как в вашем примере.


Очень глупое сравнение

Если я положу бревно на землю и мой друг побежит (с закрытыми глазами) и перепрыгнет через него, бревно все равно будет там, даже если он его не видел и не чувствовал.

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

16 голосов
/ 16 декабря 2011

Ваш код в порядке. Переменная живет там, где она будет жить, если бы goto там не было.

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

C ++ 11 6.7 Заявление декларации [stmt.dcl]

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

void f()
{
    // ...
    goto lx;    // ill-formed: jump into scope of `a'
    // ...
ly:
    X a = 1;
    // ...
lx:
    goto ly;    // ok, jump implies destructor
                // call for `a' followed by construction
                // again immediately following label ly
}

- конец примера]

9 голосов
/ 16 декабря 2011

Определения не являются исполняемым кодом.Это всего лишь инструкции для компилятора, позволяющие ему узнать размер и тип переменной.В этом смысле определение не игнорируется оператором goto.

Если вы используете класс с конструктором вместо int, вызов конструктора будет игнорироваться goto, но хранилище будет выделено в любом случае.Однако экземпляр класса остается неинициализированным, поэтому его использование до того, как строка определения / инициализации получит элемент управления, является ошибкой.

8 голосов
/ 16 декабря 2011

На мой взгляд, определение всегда означает распределение памяти.

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

2 голосов
/ 16 декабря 2011

Управление потоком не имеет ничего общего с хранилищем переменной, которое зарезервировано во время компиляции компилятором.

Оператор goto влияет только на инициализацию объекта dynamic .Для встроенных типов и типов POD это не имеет значения, поскольку они могут оставаться неинициализированными.Однако для типов не POD это приведет к ошибке компиляции.Например, см.

struct A{ A(){} };  //it is a non-POD type

void f()
{
    goto label;

    A a;     //error - you cannot skip this!

label:
    return;
}

Ошибка:

prog.cpp: In function ‘void f()’:
prog.cpp:8: error: jump to label ‘label’
prog.cpp:5: error:   from here
prog.cpp:6: error:   crosses initialization of ‘A a’

См. Здесь: http://ideone.com/p6kau

В этом примере A не относится к типу POD поскольку он имеет определяемый пользователем конструктор , что означает, что объект должен быть динамически инициализирован, но поскольку оператор goto пытается пропустить это, компилятор генерирует ошибку, как и должно быть.

Обратите внимание, что объекты только встроенных типов и типов POD могут оставаться неинициализированными.

1 голос
/ 16 декабря 2011

Позиция объявления i не имеет значения для компилятора. Вы можете доказать это себе, скомпилировав свой код с int i до goto, а затем после и сравнив сгенерированную сборку:

g++ -S test_with_i_before_goto.cpp -o test1.asm
g++ -S test_with_i_after_goto.cpp -o test2.asm
diff -u test1.asm test2.asm

Единственное отличие в этом случае - ссылка на имя исходного файла (.file).

1 голос
/ 16 декабря 2011

Короче говоря, объявление переменной является лексическим, , т.е. относится к лексическим {} -замкнутым блокам. Привязка действительна со строки, в которой она объявлена, до конца блока. На него не влияет управление потоком (goto).

Присвоение переменных переменным locol (стек), с другой стороны, является операцией времени выполнения, выполняемой, когда поток управления попадает туда. Так что goto имеет влияние на это.

Ситуация становится немного сложнее, когда начинается строительство объекта, но это не ваш случай.

0 голосов
/ 16 декабря 2011

Определение переменной НЕ выделяет память для переменной.Тем не менее, он указывает компилятору подготовить соответствующее пространство памяти для хранения переменной, но память не выделяется, когда управление прошло определение.

Здесь действительно важна инициализация.

...