Рассмотрим этот код:
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
достаточно?