Всегда ли (p + x) -x приводит к p для указателя p и целому числу x в gcc linux x86-64 C ++

/(.*)<FooBar>/s

s вызывает Dot (.) для соответствия возврату каретки

-3
задан personal_cloud 4 March 2019 в 06:25
поделиться

3 ответа

Вы не понимаете, что такое «неопределенное поведение», и я не могу винить вас, учитывая, что это часто плохо объясняется. Вот как стандарт определяет неопределенное поведение, раздел 3.27 в intro.defs:

поведение, для которого этот документ не предъявляет никаких требований

Вот и все. Ни меньше, ни больше. Стандарт можно представить как ряд ограничений, которым должны следовать поставщики компиляторов при создании допустимых программ. Когда есть неопределенное поведение, все ставки выключены.

Некоторые люди говорят, что неопределенное поведение может привести к тому, что ваша программа порождает драконов или переформатирует ваш жесткий диск, но я считаю, что это немного бессмысленно. Более реалистично, что-то вроде выхода за пределы границ массива может привести к ошибке сегмента (из-за запуска ошибки страницы). Иногда неопределенное поведение позволяет компиляторам выполнять оптимизацию, которая может неожиданно изменить поведение вашей программы, так как нет ничего, что компилятор не может .

Дело в том, что компиляторы не «генерируют неопределенное поведение». Неопределенное поведение существует в вашей программы.

Я имел в виду, что если у GCC есть замечательная функция (в частности, математика для недействительных указателей), которая в настоящее время не названа, мы можем дать ей имя, а затем потребовать его и в будущих версиях.

Тогда это будет нестандартное расширение, и можно ожидать, что оно будет задокументировано. Я также очень сомневаюсь, что такая функция будет востребована, учитывая, что она не только позволит людям писать небезопасный код, но и будет чрезвычайно трудно создавать переносимые программы.

0
ответ дан user11143275 4 March 2019 в 06:25
поделиться

Да, специально для 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 ++ для ненулевого распределения указателей массива? .

0
ответ дан Peter Cordes 4 March 2019 в 06:25
поделиться

Вот список расширений gcc. https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html

. Есть расширение для арифметики указателей. Gcc позволяет выполнять арифметику указателей на пустых указателях. (Не о том расширении, о котором вы спрашиваете.)

Итак, gcc рассматривает поведение для арифметики указателей, о которой вы спрашиваете, как неопределенное при тех же условиях, что описаны в стандарте языка.

Вы можете просмотреть там и посмотреть, есть ли что-то, что я пропустил, что относится к вашему вопросу.

0
ответ дан prl 4 March 2019 в 06:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: