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

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

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

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

Ответ 1

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

Оператор

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

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

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

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

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

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

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

Ответ 2

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

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

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

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

имеет поведение undefined. 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 (но не в том случае, если они используются осторожно).

Ответ 3

Этот код не имеет поведения undefined. Мы можем найти хороший пример в Обосновании для языков международного стандартного программирования-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

и он говорит:

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

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

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