Почему я могу перейти в область действия переменной alloca:d, но не массива переменной длины?

См. эту тестовую программу:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
  if (argc < 2)
    goto end;

  char s[strlen(argv[1]) + 1];
  strcpy(s, argv[1]);
  printf("s=%s\n", s);

end:
  return 0;
}

Не удается скомпилировать с ошибкой «перейти в область действия идентификатора с изменяемым типом» (см. -scope-of-identifier-with-variable-modified">другой вопрос).

Однако он отлично компилируется, если я изменю объявление s на это (и включу alloca.h):

char *s = alloca(strlen(argv[1]) + 1);

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


person Tor Klingberg    schedule 02.06.2017    source источник
comment
Махнув рукой, ответ заключается в том, что ветвление по области действия VLA испортит стек, но sizeof(char*) исправлено. Я добавил тег юриста, чтобы увеличить шансы получить достойный ответ.   -  person Bathsheba    schedule 02.06.2017
comment
Спасибо, язык-адвокат определенно применим. Если мне нужно угадать, то использование s после перехода за пределы alloca уже является UB, поскольку s будет неинициализированным указателем, но использование s после перехода за пределы объявления VLA было бы в порядке, если бы сам переход не был запрещен.   -  person Tor Klingberg    schedule 02.06.2017
comment
alloca не входит в стандарт, поэтому компилятор может рассматривать ее как любую другую функцию (хотя они часто нет), и в этом случае по стандарту все в порядке. Хотя, конечно, это может привести к аналогичному неопределенному поведению   -  person Kninnug    schedule 02.06.2017
comment
@Kninnug: это важный момент. ОП, не могли бы вы переформулировать вопрос без этой функции?   -  person Bathsheba    schedule 02.06.2017
comment
Я забыл, что alloca не является стандартной функцией. Этот вопрос действительно касается контраста между VLA:s и alloca. Вопрос только для VLA уже существует (здесь)[stackoverflow.com/questions/20654191/.   -  person Tor Klingberg    schedule 02.06.2017
comment
Люблю правописание ралли. Вот как моя изысканно шикарная дорогая жена произносит это слово.   -  person Bathsheba    schedule 02.06.2017


Ответы (2)


Это связано с тем, что компилятор должен инициализировать во время выполнения кадр области действия с помощью VLA. Другими словами, вы говорите ему перейти к адресу :END, но вы просите его перепрыгнуть через код инициализации фрейма этой области.

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

Представьте что-то вроде:

if (cond) goto end;
...
char a[expr];
end:
a[i] = 20;

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

Теперь о alloca. Компилятор сделает то же самое, но не сможет обнаружить segfault.

Так что это будет segfault без предупреждения/ошибки со стороны компилятора.

Логика та же, что и для VLA.

int main(int argc, char *argv[])
{
    goto end;
    char *s = alloca(100);
end:
    s[1] = 2;
    return 0;
}

Вот почему в ISO 9899 они вставили утверждение:

6.8.6.1 Оператор goto. Ограничения

1 Идентификатор в операторе goto должен называть метку, расположенную где-то во внешней функции. Оператор goto не должен переходить из-за пределов области действия идентификатора, имеющего изменяемый тип, внутрь области действия этого идентификатора.

Компилятор не может определить во время статического анализа правильный ответ для этой задачи, так как на самом деле это halting problem.

person alinsoar    schedule 02.06.2017
comment
Таким образом, нет никакой технической причины (alloca с тем же успехом мог быть malloc) для его запрета, просто стандарт C пытается сделать VLA поддающимися статическому анализу, возможно, на основании того, что они объявлены статически (но они не в любом случае полностью поддаются этому, учитывая их динамический размер). Мне кажется, что стандартизация alloca вместо VLA была бы гораздо лучшим решением. - person PSkocik; 02.06.2017
comment
@PSkocik На мой взгляд, это ограничение, налагаемое определением языка, является brute-force способом избежать halting-problem. - person alinsoar; 02.06.2017

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

Представьте, что ваша программа была расширена следующим образом:

end:
    printf("size of str: %zu\n", sizeof s);
    return 0;
}

Для версии allocasizeof s == sizeof(char*), которую можно вычислить во время компиляции, и все в порядке. Однако для версии VLA длина s неизвестна, а sizeof s невозможно вычислить.

person Kninnug    schedule 02.06.2017
comment
Ваша точка зрения хороша, я просто хочу добавить, что значение sizeof вычисляется на самом деле не в месте вызова sizeof, а в месте объявления, а значение sizeof хранится также где-то в стеке/каком-то регистре, поэтому переход непосредственно перед sizeof и переход через код инициализации в месте определения делает sizeof print огромным и т. д. - person alinsoar; 02.06.2017
comment
@alinsoar: Можете ли вы предоставить какие-либо доказательства вашего утверждения о том, как хранится размер VLA? - person Jonathan Leffler; 02.06.2017
comment
интервал [м] ; м++ ; размер(а) ; размер вычисляется при определении, поэтому хранится где-то. - person alinsoar; 02.06.2017