Использует malloc для поведения int undefined до C ++ 20

96

Мне сказали, что следующий код имеет неопределенное поведение до C ++ 20:

int *p = (int*)malloc(sizeof(int));
*p = 10;

Это правда?

Аргументом было то, что время жизни intобъекта не начинается до присвоения ему значения ( P0593R6 ). Для устранения проблемы newследует использовать размещение:

int *p = (int*)malloc(sizeof(int));
new (p) int;
*p = 10;

Неужели нам действительно нужно вызывать конструктор по умолчанию, который является тривиальным для запуска жизненного цикла объекта?

В то же время код не имеет неопределенного поведения на чистом C. Но что, если я выделю intкод на C и использую его в коде C ++?

// C source code:
int *alloc_int(void)
{
    int *p = (int*)malloc(sizeof(int));
    *p = 10;
    return p;
}

// C++ source code:
extern "C" int *alloc_int(void);

auto p = alloc_int();
*p = 20;

Это все еще неопределенное поведение?

anton_rh
источник
8
Для int? Нет. Для std::string? Да.
Эльджай,
8
@Eljay Ибо intтоже да. Просто на практике это не вызовет проблем, если вы этого не сделаете. Ведь std::stringэто, очевидно, вызовет проблемы.
Барри,
До C ++ 20 вы можете добавить новое размещение. Тогда он будет хорошо сформирован и, вероятно, ничего не будет стоить.
Франсуа Андриё,
8
Какие новые правила в C ++ 20 это меняют?
Кевин
4
Не должно быть int *p = (int*)malloc(sizeof(int)); p = new(p) int;? Однажды я понял, что не присвоение результата размещения new также может привести к фатальным последствиям (хотя это может выглядеть немного глупо).
Scheff

Ответы:

62

Это правда?

Да. Технически говоря, не входит в состав:

int *p = (int*)malloc(sizeof(int));

на самом деле создает объект типа int, поэтому разыменование p- это UB, поскольку там нет фактического int.

Неужели нам действительно нужно вызывать конструктор по умолчанию, который является тривиальным, чтобы начать время жизни объекта?

Нужно ли использовать объектную модель C ++, чтобы избежать неопределенного поведения до C ++ 20? Да. Может ли компилятор причинить вред, если вы этого не сделаете? Не то, чтобы я в курсе.

[...] Это все еще неопределенное поведение?

Да. До C ++ 20 вы по-прежнему нигде не создавали intобъект, так что это UB.

Барри
источник
Комментарии не предназначены для расширенного обсуждения; этот разговор был перемещен в чат .
Макиен
Почему языка в timsong-cpp.github.io/cppwp/n3337/basic.life#1.1 недостаточно, чтобы он не был UB? Ведь intв примере было получено хранилище нужного размера и выравнивания - intтам начинается время жизни объекта.
avakar
41

Да, это был УБ. Список способов intсуществования может быть перечислен, и ни один из них не применим, если только вы не считаете, что malloc является акаузальным.

Многие считали, что это недостаток стандарта, но маловажный, потому что оптимизация, выполненная компиляторами C ++ вокруг этого конкретного бита UB, не вызывала проблем с этим вариантом использования.

Что касается второго вопроса, C ++ не определяет, как взаимодействуют C ++ и C. Итак, все взаимодействие с C - это ... UB, иначе говоря, поведение не определено стандартом C ++.

Якк - Адам Неврамонт
источник
5
Можете ли вы расширить перечисленный список способов существования int? Я помню, как задавал аналогичный вопрос о продолжительности жизни примитивных типов, и мне сказали, что примитив может «существовать», просто говоря, что он существует, потому что в спецификации не сказано иное. Похоже, я пропустил полезный раздел спецификации! Я хотел бы знать, какой раздел я должен был просмотреть!
Корт Аммон,
7
@CortAmmon Перечислимый список способов существования объекта (любого типа) в C ++ 20 находится в [intro.object] : (1) по определению (2) по новому выражению (3) неявно согласно новым правилам в P0593 (4) временное изменение активного члена союза (5). (3) является новым в C ++ 20, (4) является новым в C ++ 17.
Барри,
3
Действительно ли взаимодействие C / C ++ UB? Было бы больше смысла быть определенным реализацией, чем неопределенным, иначе было бы странно вообще иметь extern "C"синтаксис.
Руслан
4
@Ruslan: Реализации могут определять любое поведение, которое ISO C ++ оставляет неопределенным. (Например gcc -fno-strict-aliasing, или MSVC по умолчанию). Сказать, что «реализация определена» потребует, чтобы все реализации C ++ определили какой-то способ взаимодействия с некоторой реализацией C, поэтому имеет смысл полностью оставить на усмотрение реализации, хотят они делать что-то подобное или нет.
Питер Кордес,
4
@PeterCordes: Интересно, почему так много людей не осознают это различие между IDB и UB и принимают какое-то фантастическое представление о том, что отказ Стандарта требовать, чтобы все реализации обрабатывали конструкцию, осмысленно подразумевает суждение о том, что никакие реализации не должны делать этого, и реализации, которые этого не делают, не должны как следствие рассматриваться как неполноценные.
supercat