Переинтерпретация правильно выровненных указателей на объект с объявленным типом

Стандарт позволяет нам приводить указатели на типы объектов друг к другу, если они соответствующим образом выровнены. 6.3.2.3(p7):

Указатель на тип объекта может быть преобразован в указатель на другой тип объекта. Если результирующий указатель неправильно выровнен68) для указанного типа, поведение не определено.

Стандарт позволяет нам копировать представление объекта в char[sizeof(the_object_type)] 6.2.6.1(p4):

Значение может быть скопировано в объект типа unsigned char [n] (например, с помощью memcpy); результирующий набор байтов называется объектным представлением значения.

Кроме того, в Стандарте прямо указано, что

Два значения (кроме NaN) с одним и тем же представлением объекта сравниваются как равные, но значения, которые сравниваются как равные, могут иметь разные представления объекта.

Рассмотрим следующий код:

struct contains_64_t{
    uint64_t value;
};

int main(int args, const char *argv[]){
    _Alignas(struct contains_64_t) 
        char buf_2_64t[2 * sizeof(struct contains_64_t)];
    struct contains_64_t c64_1;
    c64_1.value = 1;
    struct contains_64_t c64_2;
    c64_2.value = 2;
    memcpy(buf_2_64t, &c64_1, sizeof(c64_1));
    memcpy(buf_2_64t + sizeof(c64_1), &c64_2, sizeof(c64_2));

    //suitably aligned, ok
    struct contains_64_t *c64_ptr = (struct contains_64_t*) buf_2_64t; 
    printf("Value %"PRIu64"\n", c64_ptr -> value);
}

ВОПРОС. Педантично ли писать такой код? Если нет, то с какой проблемой мы можем столкнуться, если сделаем это?

Из того, что я вижу,

мы можем преобразовать char* в struct contains_64_t, поскольку оно правильно выровнено. Но проблема в том, что объявленный тип buf — это char[2 * sizeof(struct contains_64_t)]. Таким образом, формально говоря, мы не можем получить доступ к buf через lvalue типа struct contains_64_t *.

Но это было бы странно, так как мы правильно выровняли указатель и буквально идентичное представление объекта. Конечно, мы могли бы объявить struct contains_64_t buf[2];, но решение не сработает, если struct содержит массив переменной длины

UPD: Будет ли достаточно такого выравнивания буфера, если мы предположим, что компилируем с помощью GCC?


person Some Name    schedule 20.03.2019    source источник
comment
Выравнивание не является потенциальным UB здесь, сглаживание, возможно, связано с struct contains_64_t *c64_ptr = (struct contains_64_t*) bufprintf("Value = %lu\n", c64_ptr -> value);64t;. printf("Value = %lu\n", c64_ptr -> value); это конечно UB с 32-битной long.   -  person chux - Reinstate Monica    schedule 20.03.2019
comment
@chux Почему приведение к правильно выровненному указателю UB? Или вы имеете в виду, что UB вызван неправильным использованием printf? Исправлено.   -  person Some Name    schedule 20.03.2019
comment
Некоторые 1) приведение не является проблемой UB, сглаживание UB может применяться здесь. 2) %lu для unsigned long, не обязательно uint64_t.   -  person chux - Reinstate Monica    schedule 20.03.2019
comment
@chux Согласен с 2). Заменил его на PRIu64, определенный в inttypes.h.   -  person Some Name    schedule 20.03.2019
comment
@chux В любом случае, если мы не выровняем указатель по буферу, выделенному в стеке, мы не сможем его преобразовать. Таким образом, UB также будет иметь значение struct contains_64_t *c64_ptr = (struct contains_64_t*) buf6.2.3.2(p7)64t;, в противном случае Стандарт разрешает такое преобразование на уровне 6.2.3.2(p7).   -  person Some Name    schedule 20.03.2019


Ответы (1)


memcpy() выглядит нормально.

c64_ptr -> value is UB.

Доступ к хранимому значению объекта должен осуществляться только с помощью выражения lvalue, которое имеет один из следующих типов:

- тип, совместимый с эффективным типом объекта,

- уточненная версия типа, совместимая с действующим типом объекта,

- тип, который является подписанным или беззнаковым типом, соответствующим эффективному типу объекта,

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

- тип агрегата или объединения, который включает в себя один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член подагрегата или содержащего объединения), или

— тип персонажа.

Найдите compatible в стандарте, чтобы дополнить картину.

person Alexey Frunze    schedule 20.03.2019