GOTO перед локальной переменной

Является ли следующий fragment кода неопределенным поведением, так как я прыгаю перед объявлением переменной и использую его с помощью указателя? Если да, существуют ли различия между стандартами?

int main() { int *p = 0; label1: if (p) { printf("%d\n", *p); return 0; } int i = 999; p = &i; goto label1; return -1; } 

В вашей программе нет неопределенного поведения.

goto имеет два ограничения:

(c11, 6.8.6.1p1) «Идентификатор в инструкции goto должен обозначать метку, расположенную где-то в закрывающей функции. Оператор goto не должен выходить из-за пределов области идентификатора, имеющего измененный тип, внутри области этого идентификатор.”

что вы не нарушаете, и нет других требований, связанных с ограничениями.

Обратите внимание, что это то же самое (в смысле нет никаких дополнительных требований) в c99 и c90. Конечно, в c90, программа не была бы действительной из-за сочетания декларации и заявлений.

Что касается времени жизни объекта i при доступе после инструкции goto , C говорит (см. Мой акцент, другие скопированные предложения в следующем абзаце будут интересны для более сложной программы):

(c11, 6.2.4p6) « Для такого объекта, который не имеет тип массива переменной длины, его время жизни продолжается от входа в блок, с которым он связан, до тех пор, пока выполнение этого блока не закончится каким-либо образом. […] Если блок вводится рекурсивно, каждый раз создается новый экземпляр объекта. […] Если для объекта задана инициализация, он выполняется каждый раз, когда достигается объявление или составной литерал при выполнении блока , в противном случае значение становится неопределенным при достижении декларации ».

Это означает, что i все еще жив, когда *p читается; ни один объект не доступен за пределами его срока службы.

Я постараюсь ответить на вопрос, который вы, возможно, пытались спросить.

Поведение вашей программы четко определено. ( return -1; является проблематичным, только 0 , EXIT_SUCCESS и EXIT_FAILURE корректно определены как значения, возвращаемые из main . Но это не то, о чем вы просите.)

Эта программа:

 #include  int main(void) { goto LABEL; int *p = 0; LABEL: if (p) { printf("%d\n", *p); } } 

имеет неопределенное поведение. goto передает управление точке в пределах области p , но обходит ее инициализацию, поэтому p имеет неопределенное значение, когда выполняется проверка if (p) .

В вашей программе значение p хорошо определено в любое время. Объявление, которое достигается до goto , устанавливает p на 0 (нулевой указатель). Тест if (p) является ложным, поэтому тело оператора if не выполняется в первый раз. Функция goto выполняется после того, как p получил заданное значение, отличное от нуля. После goto проверка if (p) истинна, и выполняется вызов printf .

В вашей программе время жизни как p и i начинается, когда достигается открытие { main , и заканчивается, когда закрывается } или выполняется оператор return . Объем каждого (т. Е. Область текста программы, в которой отображается его имя) простирается от его объявления до закрытия } . Когда goto передает управление назад, имя переменной i выходит за пределы области видимости, но объект int к которому относится это имя, все еще существует. Имя p находится в области видимости (потому что оно было объявлено ранее), и объект-указатель по-прежнему указывает на тот же int объект (чье имя было бы i если бы это имя было видимым).

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

Обычно, если объявление объекта имеет инициализатор, который гарантирует, что он имеет допустимое значение, когда его имя видимо (если позднее ему назначено какое-то недопустимое значение). Это можно обойти с помощью goto или switch (но не если они используются осторожно).

Этот код не имеет неопределенного поведения. Мы можем найти хороший пример в Обосновании для международных языков стандартного программирования-C в разделе 6.2.4 Длительность хранения объектов, которые он говорит:

[…] Существует простое эмпирическое правило: объявленная переменная создается с неопределенным значением при вводе блока, но инициализатор оценивается и значение, помещенное в переменную, когда декларация достигается в ходе обычной выполнение. Таким образом, переход вперед после объявления оставляет его неинициализированным, а переход назад может привести к его инициализации более одного раза. Если декларация не инициализирует переменную, она устанавливает неопределенное значение, даже если это не первый раз, когда объявление было достигнуто.

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

и дает следующий пример:

 int j = 42; { int i = 0; loop: printf("I = %4d, ", i); printf("J1 = %4d, ", ++j); int j = i; printf("J2 = %4d, ", ++j); int k; printf("K1 = %4d, ", k); k = i * 10; printf("K2 = %4d, ", k); if (i % 2 == 0) goto skip; int m = i * 5; skip: printf("M = %4d\n", m); if (++i < 5) goto loop; } 

и выход:

  I = 0, J1 = 43, J2 = 1, K1 = ????, K2 = 0, M = ???? I = 1, J1 = 44, J2 = 2, K1 = ????, K2 = 10, M = 5 I = 2, J1 = 45, J2 = 3, K1 = ????, K2 = 20, M = 5 I = 3, J1 = 46, J2 = 4, K1 = ????, K2 = 30, M = 15 I = 4, J1 = 47, J2 = 5, K1 = ????, K2 = 40, M = 15 

и он говорит:

где «????» указывает неопределенное значение (и любое использование неопределенного значения - неопределенное поведение).

Этот пример согласуется с проектом стандарта C99 в разделе 6.2.4 Длительность хранения объектов, параграф 5, который гласит:

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