При компиляции большого проекта с clang я наткнулся на раздражающую ошибку.Ошибка оптимизации LLVM или неопределенное поведение?
Рассмотрим следующий небольшой пример:
unsigned long int * * fee();
void foo(unsigned long int q)
{
unsigned long int i,j,k,e;
unsigned long int pows[7];
unsigned long int * * table;
e = 0;
for (i = 1; i <= 256; i *= q)
pows[e++] = i;
pows[e--] = i;
table = fee(); // need to set table to something unknown
// here, otherwise the compiler optimises
// parts of the loops below away
// (and no bug occurs)
for (i = 0; i < q; i++)
for (j = 0; j < e; j++)
((unsigned char*)(*table) + 5)[i*e + j] = 0; // bug here
}
Насколько мне известно, этот код не нарушает стандарт C в любом случае, хотя последняя строка кажется неуклюжим (в реальном проекте, такой код появляется из-за чрезмерного использования макросов препроцессора).
Компиляция с помощью clang (версия 3.1 или выше) на уровне оптимизации -O1 или выше приводит к записи кода в неправильное положение в памяти.
Решающим частей файла сборки создаваемого лязгом/LLVM следующим образом: (Это синтаксис GAS, так те из вас, кто привык к Intel: Осторожно!)
[...]
callq _fee
leaq 6(%rbx), %r8 ## at this point, %rbx == e-1
xorl %edx, %edx
LBB0_4:
[...]
movq %r8, %rsi
imulq %rdx, %rsi
incq %rdx
LBB0_6:
movq (%rax), %rcx ## %rax == fee()
movb $0, (%rcx,%rsi)
incq %rsi
[conditional jumps back to LBB0_6 resp. LBB0_4]
[...]
В других слова, инструкции
(*table)[i*(e+5) + j] = 0;
вместо последней строки, указанной выше. Выбор + 5
произволен, добавление (или вычитание) других целых чисел приводит к такому же поведению. Итак - это ошибка в оптимизации LLVM или существует неопределенное поведение здесь?
Редактировать: Отметьте также, что ошибка исчезнет, если я не оставляю литой (unsigned char*)
в последней строке. В общем, ошибка кажется довольно чувствительной к любым изменениям.
Невозможно увидеть умножение на 5 в ассемблерном коде выше (но тогда я больше привык к ARM-ассемблеру, чем Intel, если это Intel :-)), но последняя строка кода C переводится в '* ((unsigned char *) (* table) + 5 + i * e + j) ', поэтому ... вы уверены, что правильно поместите эти фигурные скобки вокруг« e + 5 »в своей интерпретации выхода ассемблера? – user2116939
Да, я уверен. Это синтаксис GAS, а не Intel, поэтому 'movq% r8,% rsi' и' imulq% rdx,% rsi' означают, что '% rsi' будет удерживать' (% rbx + 6) *% rdx = (e + 5) *% rdx'. –
Да, теперь я вижу это. Это похоже на ошибку оптимизатора, так как код достаточно кошерный, даже если немного странно (но тогда макросы могут генерировать странный вывод). – user2116939