Está usando malloc para comportamento indefinido int até C ++ 20

95

Disseram-me que o código a seguir tem comportamento indefinido até C ++ 20:

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

Isso é verdade?

O argumento foi que o tempo de vida do intobjeto não é iniciado antes da atribuição do valor a ele ( P0593R6 ). Para resolver o problema, o posicionamento newdeve ser usado:

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

Precisamos realmente chamar um construtor padrão que seja trivial para iniciar a vida útil do objeto?

Ao mesmo tempo, o código não tem comportamento indefinido em C. puro. Mas, e se eu alocar um intem código C e usá-lo em código 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;

Ainda é um comportamento indefinido?

anton_rh
fonte
8
Para int? Não. Para std::string? Sim.
Eljay
8
@Eljay For int, também sim. Só que não causará problemas na prática se você não fizer isso. Pois std::string, obviamente, causará problemas.
Barry
Pré C ++ 20, você pode adicionar um novo posicionamento. Ficaria bem formado e provavelmente não custaria nada.
François Andrieux
8
Quais são as novas regras em C ++ 20 que mudam isso?
Kevin
4
Não deveria ser int *p = (int*)malloc(sizeof(int)); p = new(p) int;? Uma vez percebi que não atribuir o resultado da colocação new pode causar efeitos fatais também (embora possa parecer um pouco bobo).
Scheff

Respostas:

61

É verdade?

Sim. Tecnicamente falando, nenhuma parte de:

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

na verdade, cria um objeto do tipo int, portanto, a desreferenciação pé UB, pois não há nenhum objeto real intali.

Precisamos mesmo chamar o construtor padrão que é trivial para iniciar a vida útil do objeto?

Você precisa seguir o modelo de objeto C ++ para evitar comportamento indefinido pré-C ++ 20? Sim. Algum compilador realmente causará danos se você não fizer isso? Não que eu saiba.

[...] ainda é um comportamento indefinido?

Sim. Antes do C ++ 20, você ainda não criava um intobjeto em nenhum lugar, então este é o UB.

Barry
fonte
Os comentários não são para discussão extensa; esta conversa foi movida para o chat .
Makyen
Por que o idioma em timsong-cpp.github.io/cppwp/n3337/basic.life#1.1 não é suficiente para não ser UB? Afinal, o armazenamento de tamanho e alinhamento adequados foi obtido intno exemplo - a vida útil do intobjeto começa aí.
avakar
41

Sim, foi UB. A lista de maneiras pelas quais um intpode existir foi enumerada e nenhuma se aplica a ela, a menos que você mantenha que malloc é acausal.

Foi amplamente considerado uma falha no padrão, mas de baixa importância, porque as otimizações feitas pelos compiladores C ++ em torno daquele bit específico de UB não causaram problemas com aquele caso de uso.

Quanto à 2ª questão, C ++ não determina como C ++ e C interagem. Portanto, toda interação com C é ... UB, também conhecido como comportamento indefinido pelo padrão C ++.

Yakk - Adam Nevraumont
fonte
5
Você pode expandir a lista enumerada de maneiras de existir um int? Lembro-me de fazer uma pergunta semelhante sobre o tempo de vida de tipos primitivos, e ouvir que um primitivo poderia "existir" simplesmente dizendo que existe porque a especificação não disse o contrário. Parece que perdi uma seção útil das especificações! Eu adoraria saber qual seção eu deveria ter lido!
Cort Ammon de
7
@CortAmmon A lista enumerada de maneiras para um objeto (de qualquer tipo) existir em C ++ 20 estão em [intro.object] : (1) por definição (2) por new-expression (3) implicitamente de acordo com as novas regras em P0593 (4) mudança de membro ativo de sindicato (5) temporário. (3) é novo em C ++ 20, (4) era novo em C ++ 17.
Barry de
3
A interação C / C ++ é realmente UB? Faria mais sentido ser definido pela implementação, em vez de indefinido, caso contrário, seria estranho até mesmo ter a extern "C"sintaxe.
Ruslan
4
@Ruslan: As implementações são livres para definir qualquer comportamento que o ISO C ++ deixe indefinido. (Por exemplo gcc -fno-strict-aliasing, ou MSVC por padrão). Dizer "implementação definida" exigiria que todas as implementações C ++ definissem alguma maneira na qual interoperem com alguma implementação C, então faz sentido deixar totalmente para a implementação, quer queiram fazer algo parecido ou não.
Peter Cordes
4
@PeterCordes: Eu me pergunto por que tantas pessoas deixam de reconhecer essa distinção entre BID e UB, e adotam alguma noção fantasiosa de que a falha do Padrão em exigir que todas as implementações processem um construto de forma significativa implica um julgamento de que nenhuma implementação deveria fazer isso, e as implementações que não o fazem não devem, conseqüentemente, ser vistas como inferiores.
supercat