Как GCC реализует массивы переменной длины?
Как GCC реализует массивы переменной длины (VLA)? Являются ли такие массивы по существу указателями на динамически распределенное хранилище, например, возвращенными alloca?
Другим вариантом, о котором я мог думать, является то, что такой массив выделяется как последняя переменная в функции, так что смещение переменных известно во время компиляции. Тем не менее, смещение второй VLA снова не будет известно во время компиляции.
Ответы
Ответ 1
Здесь код распределения (x86 - код x64 похож) для следующей примерной строки, взятой из некоторых документов GCC для поддержки VLA:
char str[strlen (s1) + strlen (s2) + 1];
где расчет для strlen (s1) + strlen (s2) + 1
находится в eax
(GCC MinGW 4.8.1 - без оптимизации):
mov edx, eax
sub edx, 1
mov DWORD PTR [ebp-12], edx
mov edx, 16
sub edx, 1
add eax, edx
mov ecx, 16
mov edx, 0
div ecx
imul eax, eax, 16
call ___chkstk_ms
sub esp, eax
lea eax, [esp+8]
add eax, 0
mov DWORD PTR [ebp-16], eax
Итак, это выглядит по существу alloca()
.
Ответ 2
Ну, это всего лишь несколько диких ударов в темноте, основанных на ограничениях вокруг VLA, но в любом случае:
VLA не может быть:
- ехЬегп
- элементы структуры
- статичным
- объявлен с неопределенными границами (за исключением прототипа функции)
Все это указывает на то, что VLA выделяется в стеке, а не в кучу. Так что да, VLA, вероятно, являются последними кусками памяти стека, выделенными всякий раз, когда выделяется новый блок (как в блочной области, это петли, функции, ветки или что-то еще).
Это также объясняет, почему VLA увеличивает риск, в некоторых случаях значительно (слово предупреждения: даже не думайте об использовании VLA в сочетании с рекурсивными вызовами функций, например!).
Это также приводит к тому, что доступ к ограниченному доступу может вызвать проблемы: после завершения блока все, что указывает на то, что было памятью VLA, указывает на недопустимую память.
Но на стороне плюса: это также почему эти массивы являются потокобезопасными, хотя (из-за потоков, имеющих собственный стек) и почему они быстрее по сравнению с кучей памяти.
Размер VLA не может быть:
- Значение
extern
- ноль или отрицательный
внешнее ограничение довольно очевидно, как и ненулевое, неотрицательное... однако: если переменная, которая задает размер VLA, является подписанным int, например, компилятор не будет выдает ошибку: оценка и, следовательно, распределение VLA выполняется во время выполнения, а не во время компиляции. Следовательно, размер VLA не может и не обязательно должен быть задан во время компиляции.
Как справедливо указал MichaelBurr, VLA очень похожи на память alloca
, с одним, IMHO, решающим различием: память, выделенная alloca
, действительна с точки выделения и во всей остальной функции. VLA являются блочными областями, поэтому память освобождается после выхода из блока, в котором используется VLA:
void alloca_diff( void )
{
char *alloca_c, *vla_c;
for (int i=1;i<10;++i)
{
char *alloca_mem = alloca(i*sizeof(*alloca_mem));
alloca_c = alloca_mem;//valid
char vla_arr[i];
vla_c = vla_arr;//invalid
}//end of scope, VLA memory is freed
printf("alloca: %c\n", *alloca_c);//fine
printf("vla: %c\n\", *vla_c);//undefined behaviour... avoid!
}//end of function alloca memory is freed, irrespective of block scope