Интересно, что если вы добавите дополнительный int * ret2 в function1, тогда в моей системе порядок будет правильным, тогда как он не работает только для 3 локальных переменных. Я предполагаю, что это упорядочено таким образом, чтобы отразить стратегию распределения регистров, которая будет использоваться. Либо так, либо произвольно.
Я понятия не имею , почему GCC организует свой стек именно так (хотя я думаю, вы могли бы открыть его исходный код или эту статью и узнать), но я могу рассказать вам, как гарантировать порядок определенных переменных стека, если вам по какой-то причине это нужно. Просто поместите их в структуру:
void function1() {
struct {
int x;
int y;
int z;
int *ret;
} locals;
}
Если мне не изменяет память, спецификация гарантирует, что & ret> & z> & y> & x
. Я оставил свой K&R на работе, поэтому я не могу цитировать главы и стихи.
Обычно это связано с проблемами выравнивания.
Большинство процессоров медленнее выбирают данные, не выровненные по словам процессора. Им приходится собирать его на части и соединять вместе.
Вероятно, происходит то, что они складывают вместе все объекты, которые больше или равны оптимальному выравниванию процессора, а затем плотнее упаковывают вещи, которые могут не быть выровнены . Так уж получилось, что в вашем примере все массивы char
имеют размер 4 байта, но я уверен, что если вы сделаете их 3 байтами, они все равно окажутся в тех же местах.
Но если вы имел четыре однобайтовых массива, они могут оказаться в одном 4-байтовом диапазоне или выровнены в четыре отдельных.
Все дело в том, что проще всего (что означает «самый быстрый») для процессора.
ISO C не только ничего не говорит о порядке локальных переменных в стеке, но даже не гарантирует, что стек вообще существует. Стандарт просто говорит об объеме и времени жизни переменных внутри блока.
Я предполагаю, что это как-то связано с тем, как данные загружаются в регистры. Возможно, с массивами char компилятор творит чудеса, чтобы делать что-то параллельно, и это как-то связано с позицией в памяти, чтобы легко загружать данные в регистры. Попробуйте скомпилировать с разными уровнями оптимизации и попробуйте вместо этого использовать int buffer1 [1]
.
Это также может быть проблемой безопасности?
int main()
{
int array[10];
int i;
for (i = 0; i <= 10; ++i)
{
array[i] = 0;
}
}
Если массив ниже в стеке, чем i, этот код будет бесконечно зацикливаться (потому что он ошибочно обращается к массиву [10] и обнуляет его, что является я). При размещении массива выше в стеке попытки доступа к памяти за пределами стека с большей вероятностью будут касаться нераспределенной памяти и сбоя, а не вызывать неопределенное поведение.
Я экспериментировал с тем же кодом один раз с gcc, и не смог заставить его выйти из строя, за исключением определенной комбинации флагов, которую я сейчас не помню .. В любом случае, он поместил массив на несколько байтов от i.
Это полностью зависит от компилятора. Помимо этого, некоторые процедурные переменные могут вообще никогда не помещаться в стек, поскольку они могут проводить всю свою жизнь в регистре.
Итак, я еще немного поэкспериментировал и вот что нашел. Кажется, это основано на том, является ли каждая переменная массивом. Учитывая этот ввод:
void f5() {
int w;
int x[1];
int *ret;
int y;
int z[1];
}
Я получаю это в gdb:
(gdb) p &w
$1 = (int *) 0xbffff4c4
(gdb) p &x
$2 = (int (*)[1]) 0xbffff4c0
(gdb) p &ret
$3 = (int **) 0xbffff4c8
(gdb) p &y
$4 = (int *) 0xbffff4cc
(gdb) p &z
$5 = (int (*)[1]) 0xbffff4bc
В этом случае сначала обрабатываются int
s и указатели, последний объявляется на вершине стека и сначала объявляется ближе к дно. Затем массивы обрабатываются в направлении, противоположном тому, чем раньше объявление, тем выше в стеке. Я уверен, что для этого есть веская причина. Интересно, что это такое.