Está usando malloc para un comportamiento indefinido int hasta C ++ 20

96

Me dijeron que el siguiente código tiene un comportamiento indefinido hasta C ++ 20:

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

¿Es eso cierto?

El argumento fue que la vida útil del intobjeto no se inicia antes de asignarle el valor ( P0593R6 ). Para solucionar el problema, se newdebe utilizar la ubicación :

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

¿Realmente tenemos que llamar a un constructor predeterminado que sea trivial para iniciar la vida útil del objeto?

Al mismo tiempo, el código no tiene un comportamiento indefinido en C. puro. Pero, ¿qué intpasa si asigno un código en C y lo uso en 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;

¿Sigue siendo un comportamiento indefinido?

anton_rh
fuente
8
Para int? No. std::string¿ Para ? Si.
Eljay
8
@Eljay Para int, también sí. Es solo que no causará problemas en la práctica si no lo hace. Porque std::string, obviamente, causará problemas.
Barry
Pre C ++ 20 puede agregar una ubicación nueva. Entonces estaría bien formado y probablemente no costaría nada.
François Andrieux
8
¿Cuáles son las nuevas reglas en C ++ 20 que cambian esto?
Kevin
4
¿No debería ser así int *p = (int*)malloc(sizeof(int)); p = new(p) int;? Una vez me di cuenta de que no asignar un resultado nuevo a la ubicación también puede causar efectos fatales (aunque puede parecer un poco tonto).
Scheff

Respuestas:

62

¿Es verdad?

Si. Técnicamente hablando, no forma parte de:

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

en realidad crea un objeto de tipo int, por lo que la eliminación de referencias pes UB, ya que no hay ningún objeto real intallí.

¿Realmente tenemos que llamar al constructor predeterminado que es trivial para iniciar la vida útil del objeto?

¿ Tiene que seguir el modelo de objetos de C ++ para evitar un comportamiento indefinido antes de C ++ 20? Si. ¿Algún compilador realmente causará daño si no haces esto? No que yo supiese.

[...] ¿Sigue siendo un comportamiento indefinido?

Si. Antes de C ++ 20, todavía no creaste un intobjeto en ningún lugar, así que esto es UB.

Barry
fuente
Los comentarios no son para una discusión extensa; esta conversación se ha movido al chat .
Makyen
¿Por qué el idioma en timsong-cpp.github.io/cppwp/n3337/basic.life#1.1 no es suficiente para que no sea UB? Después de todo, inten el ejemplo se obtuvo un almacenamiento del tamaño y alineación adecuados : la vida útil del intobjeto comienza allí.
Avakar
41

Sí, fue UB. La lista de formas en que unint enumeró la puede existir, y ninguna se aplica allí, a menos que usted sostenga que malloc es causal.

Se consideró ampliamente una falla en el estándar, pero de poca importancia, porque las optimizaciones realizadas por los compiladores de C ++ alrededor de ese bit particular de UB no causaron problemas con ese caso de uso.

En cuanto a la segunda pregunta, C ++ no exige cómo interactúan C ++ y C. Entonces, toda interacción con C es ... UB, también conocido como comportamiento no definido por el estándar C ++.

Yakk - Adam Nevraumont
fuente
5
¿Puede ampliar la lista enumerada de formas para que exista un int? Recuerdo haber hecho una pregunta similar sobre la vida útil de los tipos primitivos y que me dijeron que un primitivo podría "existir" simplemente diciendo que existe porque la especificación no decía lo contrario. ¡Parece que me he perdido una sección útil de la especificación! ¡Me encantaría saber qué sección debería haber leído detenidamente!
Cort Ammon
7
@CortAmmon La lista enumerada de formas para que un objeto (de cualquier tipo) exista en C ++ 20 está en [intro.object] : (1) por definición (2) por nueva-expresión (3) implícitamente según las nuevas reglas en P0593 (4) cambio de miembro activo de un sindicato (5) temporal. (3) es nuevo en C ++ 20, (4) era nuevo en C ++ 17.
Barry
3
¿La interacción C / C ++ es realmente UB? Tendría más sentido estar definido por la implementación, en lugar de indefinido, de lo contrario sería extraño incluso tener la extern "C"sintaxis.
Ruslan
4
@Ruslan: Las implementaciones son libres de definir cualquier comportamiento que ISO C ++ deje sin definir. (Por ejemplo gcc -fno-strict-aliasing, o MSVC por defecto). Decir "implementación definida" requeriría que todas las implementaciones de C ++ definan alguna forma en la que interoperan con alguna implementación de C, por lo que tiene sentido dejar completamente a la implementación si quieren hacer algo así o no.
Peter Cordes
4
@PeterCordes: Me pregunto por qué tantas personas no reconocen esa distinción entre BID y UB, y adoptan una noción fantasiosa de que el hecho de que el Estándar no exija que todas las implementaciones procesen una construcción implica de manera significativa un juicio de que no se debe esperar que ninguna implementación lo haga, y las implementaciones que no lo hagan no deben, en consecuencia, considerarse inferiores.
supercat