Адресация стэковых переменных при компиляции с GCC
Недавно я увлекся code golfing'ом, и в треде трюков для гольфига на си увидел этот пост. (Все последующие действия выполнялись на 64-битном x86, linux 6.5.0-26-generic, gcc версии 11.4.0)
TLDR: к стэковым переменным можно обращаться как к элементам массива:
int main(void) {
int a=11, b=22, c=33, d=44, e=55, f=66;
printf("%d %d %d %d %d %d\n",
(&a)[0], (&a)[1], (&a)[2], (&a)[3], (&a)[4], (&a)[5]);
return 0;
}
При компиляции с -O0
получаем:
11 22 33 44 55 66
Поигравшись с этим функционалом, я попробовал брать за базовый указатель адреса других элементов:
int main(void){
int a=11, b=22, c=33, d=44, e=55, f=66;
printf("%d %d %d %d %d %d\n",
(&a)[0], (&a)[1], (&a)[2], (&a)[3], (&a)[4], (&a)[5]);
printf("%d %d %d %d\n", (&c)[0], (&c)[1], (&c)[2], (&c)[3]);
return 0;
}
Что привело к неожиданному результату:
11 33 22 44 55 66
33 22 44 55
33
и 22
явно перепутаны местами...
Заподозрив что-то неладное смотрим, как генерится ассемблерный код:
movl $11, -32(%rbp)
movl $22, -24(%rbp)
movl $33, -28(%rbp)
movl $44, -20(%rbp)
movl $55, -16(%rbp)
movl $66, -12(%rbp)
Тут можем удостовериться, что переменные действительно лежат в памяти не в той последовательности:
a=11|c=33|b=22|d=44|e=55|f=66
<-->|<-->|<-->|<-->|<-->|<-->
4b | 4b | 4b | 4b | 4b | 4b
Что говорит о том, что оба printf
'а отрабатывают "правильно", печатая содержимое полученных адресов.
Почему же в первом примере мы увидели вполне ожидаемое поведение, но во втором компилятор разложил переменные в другой последовательности? И как на это может влять printf
без флагов оптимизации?