Неопределенное поведение или ложное срабатывание

У меня есть некоторые фрагменты кода C++, которые при запуске в Xcode с включенным Undefined Behavior Sanitizer сообщают: «runtime error: store to misaligned address 0x7f8bcc403771 for type 'int', which requires 4 byte alignment».

Поэтому я создал небольшой тестовый пример Catch2, чтобы воспроизвести код, который мне нужно проверить во время выполнения как на Windows/x64 (MSVC), так и на Mac (Xcode 11/clang), но все работает, как и ожидалось, даже при компиляции с различными типами оптимизации ( -O2, -O3, -Ofast и др.).

Рассматриваемый код (тестовый пример Catch2):

TEST_CASE("misaligned_write", "[demo]") {
    unsigned char *data = (unsigned char*)malloc(20);
    memset(data, 0, 20);

    int *ptr = reinterpret_cast<int*>(&data[1]);
    *ptr = 0x11223344; // undefined behaviour triggered
    CHECK(static_cast<uint8_t>(data[1]) == 0x44);
    CHECK(static_cast<uint8_t>(data[2]) == 0x33);
    CHECK(static_cast<uint8_t>(data[3]) == 0x22);
    CHECK(static_cast<uint8_t>(data[4]) == 0x11);
}

Итак, мой вопрос: это неопределенное поведение, ложное срабатывание, или в коде есть что-то, что может сломаться в будущем из-за некоторых изменений флагов компилятора по умолчанию?


person vcarreira    schedule 16.10.2019    source источник
comment
Ну 0x7f8bcc403771 % 4 != 0. Вот поэтому и жалуется.   -  person Rietty    schedule 16.10.2019
comment
Демонстрация примера кода, который не ведет себя плохо, не показывает, что поведение не определено. Вы не можете опровергнуть неопределенное поведение контрпримером, каким бы тщательным он ни был. Одним из допустимых вариантов поведения UB является то, что он может действовать именно так, как вы ожидаете от кода, каким бы ни было это ожидание (поскольку допустимо любое поведение). Таким образом, независимо от того, что вы ожидаете от хорошего поведения кода, UB может вести себя одинаково каждый раз, когда вы проверяете, и все еще быть UB.   -  person François Andrieux    schedule 16.10.2019
comment
int *ptr = reinterpret_cast<int*>(&data[1]); *ptr = 0x11223344; это УБ. В ptr нет int, поэтому вы не можете записать его. Это может измениться в будущем (об этом есть предложение), но пока это не будет стандартизировано, это UB.   -  person NathanOliver    schedule 16.10.2019
comment
В принципе это должно работать, но у вас нет никаких гарантий от стандарта поведения программы.   -  person NathanOliver    schedule 16.10.2019
comment
@NathanOliver, у тебя случайно нет ссылки на это предложение?   -  person rustyx    schedule 16.10.2019
comment
Обратите внимание, что reinterpret_cast определяется только в том случае, если вы приводите некоторый тип A к B (но не используете его как B), а затем обратно к A. Другие варианты использования — неопределенное поведение. Например, это полезно, когда вам нужно передать данные функции обратного вызова, которая требует от вас приведения ваших данных к void *. Затем внутри обратного вызова вы возвращаете его к исходному типу.   -  person darcamo    schedule 16.10.2019
comment
@rustyx Нашел: open-std.org /jtc1/sc22/wg21/docs/papers/2019/p0593r3.html   -  person NathanOliver    schedule 16.10.2019
comment
Знание неопределенного поведения просто: либо вы это знаете, либо не знаете. Вы не можете опровергнуть это примером кода, который ведет себя правильно. Например, я мог бы, вероятно, целую вечность пытаться заставить char *p = new char [10]; delete p; потерпеть неудачу, но я знаю, что delete p; — это неопределенное поведение. Почему я это знаю? Потому что стандартный документ сказал нам, что это так.   -  person PaulMcKenzie    schedule 16.10.2019
comment
Я считаю, что вы можете избежать UB, выполнив: int int_data = 0x11223344; memcpy(&data[1], &int_data, sizeof int_data);. Обратите внимание, что существует специфичное для платформы поведение в отношении порядка байтов и размера int; Я предполагаю, что эти вещи не имеют значения для вашего варианта использования.   -  person Eljay    schedule 16.10.2019
comment
Что произойдет, если вы используете упаковку структур (__attribute__((packed))) вместо reinterpret_cast, активирует ли это также UBsan?   -  person rustyx    schedule 16.10.2019


Ответы (1)


malloc() всегда будет возвращать указатель, соответствующим образом выровненный для любого возможного типа данных. &data[1] — это указатель на второй байт, который явно не правильно выровнен для любого типа данных, имеющего требование выравнивания > 1. Используйте &data[0] или &data, если вы хотите иметь указатель в первых четырех байтах распределенной памяти.

person TeaRex    schedule 16.10.2019