Да, в частности, для gcc5.x и более поздних версий это конкретное выражение оптимизируется очень рано до p
, даже если оптимизация отключена, независимо от возможного времени выполнения UB.
Это происходит даже со статическимпостоянный размер массива и времени компиляции.gcc -fsanitize=undefined
также не вставляет никаких инструментов для его поиска.Также нет предупреждений на -Wall -Wextra -Wpedantic
int *add(int *p, long long x) {
return (p+x) - x;
}
int *visible_UB(void) {
static int arr[100];
return (arr+200) - 200;
}
Использование gcc -dump-tree-original
для выгрузки своего внутреннего представления программной логики до любых проходов оптимизации показывает, что эта оптимизация произошла еще до этого в gcc5.x и новее .(И происходит даже в -O0
).
;; Function int* add(int*, long long int) (null)
;; enabled by -tree-original
return <retval> = p;
;; Function int* visible_UB() (null)
;; enabled by -tree-original
{
static int arr[100];
static int arr[100];
return <retval> = (int *) &arr;
}
Это из проводника компилятора Godbolt с gcc8.3 с -O0
.
x86-64выход asm просто:
; g++8.3 -O0
add(int*, long long):
mov QWORD PTR [rsp-8], rdi
mov QWORD PTR [rsp-16], rsi # spill args
mov rax, QWORD PTR [rsp-8] # reload only the pointer
ret
visible_UB():
mov eax, OFFSET FLAT:_ZZ10visible_UBvE3arr
ret
-O3
выход, конечно, просто mov rax, rdi
gcc4.9 и более ранние версииэта оптимизация в последующем проходе, а не в -O0
: дамп дерева все еще включает в себя вычитание, а асм x86-64 равен
# g++4.9.4 -O0
add(int*, long long):
mov QWORD PTR [rsp-8], rdi
mov QWORD PTR [rsp-16], rsi
mov rax, QWORD PTR [rsp-16]
lea rdx, [0+rax*4] # RDX = x*4 = x*sizeof(int)
mov rax, QWORD PTR [rsp-16]
sal rax, 2
neg rax # RAX = -(x*4)
add rdx, rax # RDX = x*4 + (-(x*4)) = 0
mov rax, QWORD PTR [rsp-8]
add rax, rdx # p += x + (-x)
ret
visible_UB(): # but constants still optimize away at -O0
mov eax, OFFSET FLAT:_ZZ10visible_UBvE3arr
ret
Это совпадает с -fdump-tree-original
вывод:
return <retval> = p + ((sizetype) ((long unsigned int) x * 4) + -(sizetype) ((long unsigned int) x * 4));
Если x*4
переполнится, вы все равно получите правильный ответ.На практике я не могу придумать, как написать функцию, которая бы приводила к тому, что UB вызывает заметное изменение поведения.
Как часть более крупной функции, компиляторразрешено выводить некоторую информацию о диапазоне, например, p[x]
является частью того же объекта, что и p[0]
, так что чтение памяти между / вне этого допускается и не будет вызывать ошибки.например, разрешить автоматическую векторизацию цикла поиска.
Но я сомневаюсь, что gcc даже ищет это, не говоря уже об этом.
(Обратите внимание, что заголовок вашего вопроса был связан с таргетингом на gccx86-64 в Linux, не о том, безопасны ли подобные вещи в gcc, например, если сделано в отдельных инструкциях. Я имею в виду, да, возможно, безопасно на практике, но не будет оптимизировано почти сразу после разбора.определенно не о C ++ в целом.)
Я настоятельно рекомендую не делать это.Используйте uintptr_t
для хранения значений, похожих на указатели, которые не являются действительными указателями. Как вы делаете в обновлениях к своему ответу на расширение C ++ gcc для ненулевого распределения указателей массива? .