/(.*)<FooBar>/s
s вызывает Dot (.) для соответствия возврату каретки
Вы не понимаете, что такое «неопределенное поведение», и я не могу винить вас, учитывая, что это часто плохо объясняется. Вот как стандарт определяет неопределенное поведение, раздел 3.27 в intro.defs:
поведение, для которого этот документ не предъявляет никаких требований
blockquote>Вот и все. Ни меньше, ни больше. Стандарт можно представить как ряд ограничений, которым должны следовать поставщики компиляторов при создании допустимых программ. Когда есть неопределенное поведение, все ставки выключены.
Некоторые люди говорят, что неопределенное поведение может привести к тому, что ваша программа порождает драконов или переформатирует ваш жесткий диск, но я считаю, что это немного бессмысленно. Более реалистично, что-то вроде выхода за пределы границ массива может привести к ошибке сегмента (из-за запуска ошибки страницы). Иногда неопределенное поведение позволяет компиляторам выполнять оптимизацию, которая может неожиданно изменить поведение вашей программы, так как нет ничего, что компилятор не может .
Дело в том, что компиляторы не «генерируют неопределенное поведение». Неопределенное поведение существует в вашей программы.
Я имел в виду, что если у GCC есть замечательная функция (в частности, математика для недействительных указателей), которая в настоящее время не названа, мы можем дать ей имя, а затем потребовать его и в будущих версиях.
blockquote>Тогда это будет нестандартное расширение, и можно ожидать, что оно будет задокументировано. Я также очень сомневаюсь, что такая функция будет востребована, учитывая, что она не только позволит людям писать небезопасный код, но и будет чрезвычайно трудно создавать переносимые программы.
Да, специально для 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 и новее [ 1135]. (И происходит даже в -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;
}
Это из проводника компилятора Годболта с gcc8.3 с -O0
.
Вывод asm x86-64 просто:
; 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]
, поэтому чтение памяти в между / out, что разрешено и не будет segfault. например разрешить автоматическую векторизацию цикла поиска.
Но я сомневаюсь, что gcc даже ищет это, не говоря уже об этом.
(Обратите внимание, что заголовок вашего вопроса был специфичен для gcc, нацеленного на x86-64 в Linux, не о том, безопасны ли подобные вещи в gcc, например, если сделано в отдельных утверждениях. Я имею в виду, да, вероятно, безопасно на практике , но не будет оптимизирован почти сразу после разбора. И определенно не о C ++ в целом.)
Я настоятельно рекомендую не делать это. Используйте uintptr_t
для хранения значений, похожих на указатели, которые не являются действительными указателями. как вы делаете в обновлениях к своему ответу по расширению gcc для C ++ для ненулевого распределения указателей массива? .
Вот список расширений gcc. https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html
. Есть расширение для арифметики указателей. Gcc позволяет выполнять арифметику указателей на пустых указателях. (Не о том расширении, о котором вы спрашиваете.)
Итак, gcc рассматривает поведение для арифметики указателей, о которой вы спрашиваете, как неопределенное при тех же условиях, что описаны в стандарте языка.
Вы можете просмотреть там и посмотреть, есть ли что-то, что я пропустил, что относится к вашему вопросу.