На каких компиляторах или с какими флагами компилятора нарушение правил арифметики указателей может вызвать проблемы? - PullRequest
0 голосов
/ 29 сентября 2018

Стандарт c ++ ограничивает арифметику указателей, выполняемую в массиве ( [expr.add] ), что затрудняет реализацию вектороподобных контейнеров.

Можно реализовать вектор-как контейнер с реализацией, подобной этой:

//First approach

//Allocation
auto buffer = new unsigned char[2*sizeof(int)];
//Construction
auto p=new(buffer) int{};
new(p+1) int{};
//Example of use of an iterator, assign 10 to the second element.
*(p+1)=10;//UB p+1 is a pointer past the end of an object.

Этот предыдущий фрагмент кода иллюстрирует, как приблизительно std::vector реализован в libstdc ++ и libc ++. кажется , что компиляторы принимают этот вид кода как расширение языка c ++.

Если я хочу быть совместимым со стандартом, я мог бы реализовать vector и связанные с ним iterator втаким образом, что операции, выполняемые над вектором и его итератором, могут быть упрощены до следующего кода:

//Second approach

//Allocation:
auto buffer = new unsigned char[2*sizeof(int)];
//Construction
new(buffer) int{};
new(buffer+sizeof(int)) int{};
//Example of use of an iterator assign 10 to the second element
*(std::launder(reinterpret_cast<int*>(buffer+sizeof(int))))=10;

(Первый вопрос, не является ли этот подход также UB? Здесь арифметика указателей выполняется над массивом без знакаchar, который обеспечивает хранение для объектов int. launder используется, потому что buffer и объекты int не являются взаимозаменяемыми указателями)

Проблема с этим вторым подходом - это код, сгенерированный компилятором (GCC):

#include <new>

int test_approach_1(unsigned char* buffer){
    //Construction
    auto p = new(buffer) int{};
    new(p+1) int{10};
    //Example of use of an iterator assign 10 to the second element
    *(p+1)=13;//UB
    return *(p+1);//UB
}

int test_approach_2(unsigned char* buffer){
    //Construction
    new(buffer) int{};
    new(buffer+sizeof(int)) int{10};
    //Example of use of an iterator assign 10 to the second element
    *(std::launder(reinterpret_cast<int*>(buffer+sizeof(int))))=13;
    return *(std::launder(reinterpret_cast<int*>(buffer+sizeof(int))));
}

Генерируемая сборка:

test_approach_1(unsigned char*):
        movabs  rax, 55834574848
        mov     QWORD PTR [rdi], rax
        mov     eax, 13
        ret
test_approach_2(unsigned char*):
        movabs  rax, 42949672960
        mov     QWORD PTR [rdi], rax
        mov     eax, 13
        mov     DWORD PTR [rdi+4], 13
        ret

Код, сгенерированный для test_approach_1, является оптимальным.Поэтому я думаю, что я не буду использовать второй подход (и у меня была бы еще одна причина не использовать его, если кто-то показывает, что это также UB.)

Я не нахожу документацию для этих расширений языка, которыйПозвольте нам реализовать векторные контейнеры, используя первый подход (это UB в соответствии со стандартом).Есть ли документация для этого?На каком компиляторе можно ожидать его работы и с какими флагами компилятора?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...