Можно ли использовать указатель на память как инициализированный тривиальный тип?

Является ли неопределенным поведением использование тривиального типа без инициализации?

void* mem = malloc(sizeof(uint64_t)*100);
void* num_mem = mem + sizeof(uint64_t)*31;
//does the lifetime of uint64_t starts here:
uint64_t* mynum = reinterpret_cast<uint64_t*>(num_mem); //?
*mynum = 5; //is it UB?
std::cout << *mynum << std::endl; //is it UB?
free(mem);

Я обнаружил, что вызов деструктора тривиальных/POD/агрегатных типов не нужен, но не могу найти то же самое для начала жизни тривиального/POD/агрегатного типа. Мне нужно было звонить new(num_mem) uint64_t; вместо reinterpret_cast ..?

Изменяется ли поведение, если это POD или агрегатный объект без конструкторов?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
74
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Все нижеследующее следует правилам C++20, хотя неявное создание объекта также считается сообщением о дефекте в более ранних версиях. До С++ 17 значение значений указателя было совсем другим, поэтому обсуждение в ответе и комментариях может не применяться. Все это также строго соответствует тому, что гарантирует стандарт. Конечно, на практике компиляторы могут позволить больше.


Да, если тип и все типы его подобъектов являются неявными типами времени жизни, что является более слабым требованием, чем тривиальность или POD. Это применимо к агрегатным типам, но не обязательно к подобъектам агрегатного типа, и в этом случае их все равно необходимо явно newed (например, рассмотрим член struct A { std::string s; };).

Вы также должны убедиться, что размер и выравнивание памяти подходят. std::malloc возвращает выравнивание по памяти не менее строгое, чем std::max_align_t, поэтому это хорошо для любого скалярного типа, но не для типов с чрезмерным выравниванием.

Кроме того, std::malloc — это одна из немногих функций, специально предназначенных для неявного создания объектов и возврата указателя на один из них. Обычно это не работает для произвольной памяти. Например, если вы используете ячейку памяти как uint64_t, то неявно созданный объект будет uint64_t. Последующее приведение к другому типу приведет к нарушению алиасинга.

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

Также mem + sizeof(uint64_t)*31 является расширением GNU. В стандартном C++ невозможно выполнить арифметические операции с указателями void*. Вам нужно сначала привести к типу элемента, а затем выполнить арифметику, предполагая, что вы храните в памяти только те же типы. В противном случае все становится немного сложнее.

(Кроме того, вам не хватает проверки нулевого указателя для возвращаемого значения malloc.)

интересно, так что это четко определенный код, пока я не забочусь о выравнивании. Я специально создал буфер размером 800 байт, чтобы избежать какой-либо специализации по типу.

Arkady 27.11.2022 13:39

что касается арифметики указателя, я предполагаю, что указатель на char поможет?

Arkady 27.11.2022 13:41

@Arkady Alignment всегда актуален, поскольку невыровненные объекты вообще не могут существовать в стандартном C++ (или, по крайней мере, их время жизни никогда не может начаться). Вы не можете повторно использовать память для разных типов в одном и том же месте без вмешательства new или какой-либо другой функции, которая указана для неявного создания объектов (например, memcpy).

user17732522 27.11.2022 14:20

@Аркадий Да, а лучше unsigned char (в стандарте есть мелкая недоработка касаемо char). Однако это будет означать, что неявное создание объекта создаст массив (unsigned) char в хранилище, а затем создаст объект целевого типа, вложенный в этот массив. Указатель, возвращенный из malloc, указывает на элемент char, а не на вложенный объект. Следовательно, в этом случае вам нужно будет std::launder указатель после приведения. В любом случае это не меняет того, что я сказал выше: повторное использование одного и того же места в памяти для нескольких типов все равно приведет к UB.

user17732522 27.11.2022 14:21

если я вас правильно понял, поскольку только я интерпретирую память, возвращаемую из std::malloc, как тип неявного времени жизни - это начало его жизни. И время жизни благополучно закончится в момент освобождения памяти. Но как только я переосмысливаю память за таким объектом как объект другого типа -- это УБ.

Arkady 27.11.2022 14:58

есть ли статья, которую можно прочитать на эту тему, касающуюся С++ 11?

Arkady 27.11.2022 15:00

@Arkady Lifetime заканчивается, когда вызывается (псевдо) деструктор, память освобождается или повторно используется для другого объекта. Это справедливо для всех типов (по крайней мере, начиная с C++20). Да, вся идея в том, что malloc может сделать аналог неявного new для некоторых типов. Попытка переинтерпретировать этот неявный объект как другой тип является нарушением алиасинга по тем же правилам, что и для явно newed объекта.

user17732522 27.11.2022 15:03

@Arkady До C++ 20 в стандарте не было ничего, разрешающего неявное создание объекта (но это было исправлено как отчет о дефекте). До того, как значения указателя C++17 определялись их адресом, а не конкретным объектом, на который они указывают, и я думаю, что в результате многие из этих вещей не были точно определены. Я могу дать вам ссылку на документ, в котором представлены изменения, если я его найду.

user17732522 27.11.2022 15:06

Другие вопросы по теме