Адресация стэковых переменных при компиляции с 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 без флагов оптимизации?


Ответы (0 шт):