У меня есть следующая программа, которая включает бит проверки выравнивания (AC) в регистре флагов процессора x86, чтобы улавливать неравномерные обращения к памяти. Тогда программа объявляет две летучие переменные:Почему компилятор генерирует такой код при инициализации изменчивого массива?
#include <assert.h>
int main(void)
{
#ifndef NOASM
__asm__(
"pushf\n"
"orl $(1<<18),(%esp)\n"
"popf\n"
);
#endif
volatile unsigned char foo[] = { 1, 2, 3, 4, 5, 6 };
volatile unsigned int bar = 0xaa;
return 0;
}
Если я скомпилировать этот код, сгенерированные изначально делает на очевидные вещи, как создание стека и создать массив символов, перемещая значения 1, 2, 3, 4, 5, 6 на стек:
/tmp ➤ gcc test3.c -m32
/tmp ➤ gdb ./a.out
(gdb) disassemble main
0x0804843d <+0>: push %ebp
0x0804843e <+1>: mov %esp,%ebp
0x08048440 <+3>: and $0xfffffff0,%esp
0x08048443 <+6>: sub $0x20,%esp
0x08048446 <+9>: mov %gs:0x14,%eax
0x0804844c <+15>: mov %eax,0x1c(%esp)
0x08048450 <+19>: xor %eax,%eax
0x08048452 <+21>: pushf
0x08048453 <+22>: orl $0x40000,(%esp)
0x0804845a <+29>: popf
0x0804845b <+30>: movb $0x1,0x16(%esp)
0x08048460 <+35>: movb $0x2,0x17(%esp)
0x08048465 <+40>: movb $0x3,0x18(%esp)
0x0804846a <+45>: movb $0x4,0x19(%esp)
0x0804846f <+50>: movb $0x5,0x1a(%esp)
0x08048474 <+55>: movb $0x6,0x1b(%esp)
0x08048479 <+60>: mov 0x16(%esp),%eax
0x0804847d <+64>: mov %eax,0x10(%esp)
0x08048481 <+68>: movzwl 0x1a(%esp),%eax
0x08048486 <+73>: mov %ax,0x14(%esp)
0x0804848b <+78>: movl $0xaa,0xc(%esp)
0x08048493 <+86>: mov $0x0,%eax
0x08048498 <+91>: mov 0x1c(%esp),%edx
0x0804849c <+95>: xor %gs:0x14,%edx
0x080484a3 <+102>: je 0x80484aa <main+109>
0x080484a5 <+104>: call 0x8048310 <[email protected]>
0x080484aa <+109>: leave
0x080484ab <+110>: ret
Однако при main+60
он делает что-то странное: он перемещает массив из 6 байт в другой части стека: данные перемещаются один 4-байтовое слово в то время, в регистрах. Но байты начинаются со смещения 0x16, который не выровнен, поэтому программа будет сбой при попытке выполнить mov
.
Так что я имею два вопроса:
Почему компилятор излучающие код, чтобы скопировать массив в другую часть стека? Я предположил, что
volatile
пропускает каждую оптимизацию и всегда выполняет обращения к памяти. Может быть, волатильные вары необходимы всегда для доступа в виде целых слов, и поэтому компилятор всегда будет использовать временные регистры для чтения/записи целых слов?Почему компилятор не помещает массив символов по выровненному адресу, если позже он намеревается выполнить эти вызовы
mov
? Я понимаю, что x86, как правило, безопасен с неравномерным доступом, а на современных процессорах он даже не нести штраф за производительность; однако во всех других случаях я вижу, что компилятор пытается избежать генерации несвязанных обращений, поскольку они считаются AFAIK, неопределенным поведением в C. Я предполагаю, что, поскольку позже он обеспечивает правильно выровненный указатель для скопированного массива в стеке, он просто не заботится о выравнивании данных, используемых только для инициализации, таким образом, который невидим для программы C?
Если мои предположения выше правильно, это означает, что я не могу ожидать x86 компилятор всегда создавать выровненные доступы, даже если скомпилированный код не пытается выполнить выровненным доступ себя, и поэтому установка флага AC не практический способ обнаружения частей кода, в которых выполняются безусловные обращения.
EDIT: После дальнейших исследований я могу ответить на большинство из них сам. В попытке добиться прогресса я добавил параметр в Redis, чтобы установить флаг AC и в противном случае работать нормально. Я обнаружил, что этот подход не является жизнеспособным: процесс немедленно падает в libc: __mempcpy_sse2() at ../sysdeps/x86_64/memcpy.S:83
. Я предполагаю, что весь пакет программного обеспечения x86 просто не заботится о несоосности, поскольку эта архитектура очень хорошо обрабатывается. Таким образом, работать с установленным флагом AC нецелесообразно.
Таким образом, ответ на вопрос 2 выше заключается в том, что, как и весь пакет программного обеспечения, компилятор может делать все, что ему нравится, и перемещать вещи в стеке, не заботясь о выравнивании, если поведение правильное с точки зрения программы С.
Вопрос только в том, почему с volatile
, является ли копией в другой части стека? Лучше всего предположить, что компилятор пытается получить доступ к целым словам в объявленных переменных volatile
даже во время инициализации (представьте, был ли этот адрес сопоставлен с портом ввода-вывода), но я не уверен.
Вы должны скомпилировать с 'gcc -m32 -fverbose-asm -Wall -O -S test3.c' и посмотреть в сгенерированный' test3.s'. Вы можете использовать инструкцию 'volatile asm' –
Basile Я тоже пытался это сделать, но сгенерированный код в основном идентичен, включая смещенный доступ. – antirez
«Я не могу ожидать, что компилятор x86 всегда будет генерировать согласованные обращения, даже если скомпилированный код на самом деле никогда не пытается выполнить неприсоединившиеся обращения». Это правда. Скорее всего, вы обнаружите, что 'memcpy' всегда будет генерировать неприсоединенные обращения при передаче источника и адресата, которые не выровнены одинаково. Он выровнят источник и опустит место назначения, но все равно использует 'rep mov' с операндом' dword' . –