std :: отмывание и строгое правило алиасинга - PullRequest
0 голосов
/ 06 июля 2018

Рассмотрим этот код:

void f(char * ptr)
{
    auto int_ptr = reinterpret_cast<int*>(ptr); // <---- line of interest 
    // use int_ptr ... 
}

void example_1()
{
    int i = 10;    
    f(reinterpret_cast<char*>(&i));
}

void example_2()
{
    alignas(alignof(int)) char storage[sizeof(int)];
    new (&storage) int;
    f(storage);
}

линия интереса с вызовом из example_1:

Q1: На стороне вызова указатель char накладывается на наш целочисленный указатель. Это действительно Но действительно ли также просто привести его обратно к int? Мы знаем, что int находится в пределах своего времени жизни, но учтите, что функция определена в другой единице перевода (без включенной оптимизации времени соединения), и контекст не известен. Затем все, что видит компилятор: указатель int хочет присвоить псевдониму указатель на символ, и это нарушает строгие правила псевдонимов. Так это разрешено?

Q2: Учитывая, что это не разрешено. Мы получили std ::андер в C ++ 17. Его вид барьера оптимизации указателя в основном используется для доступа к объекту, который получил новое размещение в хранилище объекта другого типа или когда задействованы константные члены. Можем ли мы использовать его для подсказки компилятору и предотвращения неопределенного поведения?

линия интереса с вызовом из example_2:

Q3: Здесь необходимо указать std :: launder, так как это описанный в Q2 сценарий использования std :: launder, верно?

auto int_ptr = std::launder(reinterpret_cast<int*>(ptr));

Но рассмотрим еще раз f определено в другой единице перевода. Как компилятор может узнать о нашем новом размещении, которое происходит на обратной стороне? Как может компилятор (видя только функцию f) различать example_1 и example_2? Или все вышеперечисленное является просто гипотетическим, поскольку строгое правило псевдонимов просто исключает все (помните, что char * to int * не разрешено) и компилятор может делать то, что он хочет?

Следующий вопрос:

Q4: Если весь приведенный выше код неверен из-за правил наложения имен, рассмотрите возможность изменения функции f для получения пустого указателя:

void f(void* ptr)
{
    auto int_ptr = reinterpret_cast<int*>(ptr); 
    // use int_ptr ... 
}

Тогда у нас нет проблем с алиасами, но все же есть случай std::launder для example_2. Должны ли мы изменить callside и переписать нашу example_2 функцию на:

void example_2()
{
    alignas(alignof(int)) char storage[sizeof(int)];
    new (&storage) int;
    f(std::launder(storage));
}

или std::launder в функции f достаточно?

1 Ответ

0 голосов
/ 06 июля 2018

Строгое правило псевдонимов - это ограничение типа glvalue, фактически используемого для доступа к объекту. Все, что имеет значение для целей этого правила, это: а) фактический тип объекта и б) тип glvalue, используемого для доступа.

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

f действует до тех пор, пока ptr фактически указывает на объект типа int, предполагая, что он обращается к этому объекту через int_ptr без дальнейшего приведения.

example_1 действительно как написано; reinterpret_cast не изменяют значение указателя.

example_2 недопустим, потому что он дает f указатель, который на самом деле не указывает на объект int (он указывает на устаревший первый элемент массива storage). См. Есть ли (семантическая) разница между возвращаемым значением размещения new и приведенным значением его операнда?

...