www org mapwindows mapwindow español c++

c++ - org - mapwindows 5



¿Es seguro reutilizar una ubicación de memoria? (8)

Dependiendo de la definición de Data , su código puede estar roto. Es un mal código, de cualquier manera.

Si Data es un tipo de datos antiguo simple (POD, es decir, un typedef para un tipo básico, una estructura de tipos de POD, etc.), y la memoria asignada está correctamente alineada para el tipo (*), entonces su código está bien definido , lo que significa que "funcionará" (siempre que inicialice cada miembro de *data_d antes de usarlo), pero no es una buena práctica. (Vea abajo.)

Si los Data no son de tipo POD, se dirige a problemas: la asignación del puntero no habría invocado ningún constructor, por ejemplo. data_d , que es de tipo "puntero a Data ", estaría mintiendo efectivamente porque apunta a algo, pero ese algo no es de tipo Data porque no se ha creado / construido / inicializado tal tipo. El comportamiento indefinido no estará muy lejos en ese punto.

La solución para construir correctamente un objeto en una ubicación de memoria dada se llama colocación nueva :

Data * data_d = new (data) Data();

Esto le indica al compilador que construya un objeto de Data en los data ubicación . Esto funcionará para los tipos POD y no POD por igual. También deberá llamar al destructor ( data_d->~Data() ) para asegurarse de que se ejecuta antes de delete la memoria.

Tenga cuidado de no mezclar nunca las funciones de asignación / liberación. Cualquier cosa que malloc() necesite ser free() d, lo que se asigna con new necesidades se delete , y si es new [] tendrá que delete [] . Cualquier otra combinación es UB.

En cualquier caso, se desaconseja el uso de punteros "desnudos" para la propiedad de la memoria en C ++. Usted debería

  1. poner new en un constructor y la delete correspondiente en el destructor de una clase, haciendo que el objeto sea el propietario de la memoria (incluida la desasignación adecuada cuando el objeto sale del alcance, por ejemplo, en el caso de una excepción); o

  2. use un puntero inteligente que efectivamente haga lo anterior por usted.

(*): Se sabe que las implementaciones definen tipos "extendidos", cuyos requisitos de alineación no son tomados en cuenta por malloc (). No estoy seguro si los abogados de idiomas todavía los llamarían "POD", en realidad. MSVC, por ejemplo, realiza una alineación de 8 bytes en malloc () pero define el tipo extendido SSE __m128 como que tiene un requisito de alineación de 16 bytes .

Esta pregunta se basa en algún código C existente portado a C ++. Solo me interesa saber si es "seguro". Ya sé que no lo habría escrito así. Soy consciente de que el código aquí es básicamente C en lugar de C ++, pero está compilado con un compilador de C ++ y sé que los estándares son ligeramente diferentes a veces.

Tengo una función que asigna algo de memoria. Lanzo el void* devuelto void* a un int* y empiezo a usarlo.

Más tarde lanzo el void* devuelto void* a un Data* y empiezo a usarlo.

¿Es esto seguro en C ++?

Ejemplo:

void* data = malloc(10000); int* data_i = (int*)data; *data_i = 123; printf("%d/n", *data_i); Data* data_d = (Data*)data; data_d->value = 456; printf("%d/n", data_d->value);

Nunca leo las variables utilizadas a través de un tipo diferente al que se almacenaron, pero me preocupa que el compilador pueda ver que data_i y data_d son tipos diferentes y, por lo tanto, no pueden data_d legalmente entre sí y decidir reordenar mi código, por ejemplo, colocar la tienda en data_d antes del primera printf Lo que rompería todo.

Sin embargo, este es un patrón que se usa todo el tiempo. Si inserta una free y malloc entre los dos accesos, no creo que altere nada, ya que no toca la memoria afectada y puede reutilizar los mismos datos.

¿Mi código está roto o es "correcto"?


Efectivamente, ha implementado su propio asignador encima de malloc / free que reutiliza un bloque en este caso. Eso es perfectamente seguro. Los envoltorios de asignación ciertamente pueden reutilizar bloques siempre que el bloque sea lo suficientemente grande y provenga de una fuente que garantice una alineación suficiente (y malloc hace).


Está "bien", funciona como lo ha escrito (suponiendo primitivas y tipos de datos simples (POD)). Es seguro. Es efectivamente un administrador de memoria personalizado.

Algunas notas:

  • Si se crean objetos con destructores no triviales en la ubicación de la memoria asignada, asegúrese de que se llame

    obj->~obj();

  • Si crea objetos, considere la colocación de una nueva sintaxis sobre un molde simple (también funciona con POD)

    Object* obj = new (data) Object();

  • Compruebe si hay un nullptr (o NULL ), si malloc falla, se devuelve NULL

  • La alineación no debería ser un problema, pero siempre tenga en cuenta al crear un administrador de memoria y asegúrese de que la alineación sea adecuada

Dado que está utilizando un compilador de C ++, a menos que desee mantener la naturaleza "C" en el código, también puede buscar el operator new() global operator new() .

Y como siempre, una vez hecho esto, no olvide el free() (o delete si usa new )

Menciona que todavía no va a convertir ninguno de los códigos; pero si o si lo considera, hay algunas características idiomáticas en C ++ que puede desear usar sobre el malloc o incluso el global ::operator new .

Debería buscar el puntero inteligente std::unique_ptr<> o std::shared_ptr<> y permitir que se encarguen de los problemas de administración de memoria.


Las reglas que rodean el alias estricto pueden ser bastante complicadas.

Un ejemplo de alias estricto es:

int a = 0; float* f = reinterpret_cast<float*>(&a); f = 0.3; printf("%d", a);

Esta es una violación estricta de alias porque:

  • La vida útil de las variables (y su uso) se superponen
  • están interpretando el mismo recuerdo a través de dos "lentes" diferentes

Si no está haciendo ambas cosas al mismo tiempo, su código no viola el alias estricto.

En C ++, la vida útil de un objeto comienza cuando termina el constructor y se detiene cuando comienza el destructor.

En el caso de los tipos incorporados (sin destructor) o POD (destructor trivial), la regla es que su vida útil finaliza cada vez que se sobrescribe o libera la memoria.

Nota: esto es específicamente para admitir la escritura de administradores de memoria; después de todo, malloc se escribe en C y el operator new se escribe en C ++ y se les permite explícitamente agrupar la memoria.

Usé lentes específicamente en lugar de tipos porque la regla es un poco más difícil.

C ++ generalmente usa la tipificación nominal : si dos tipos tienen un nombre diferente, son diferentes. Si accede a un valor de tipo dinámico T como si fuera una U , está violando el alias.

Hay una serie de excepciones a esta regla:

  • acceso por clase base
  • en los POD, acceda como puntero al primer atributo

Y la regla más complicada está relacionada con la union donde C ++ se desplaza hacia la tipificación estructural : puede acceder a un fragmento de memoria a través de dos tipos diferentes, si solo accede a partes al comienzo de este fragmento de memoria en el que los dos tipos comparten una inicial común secuencia.

§9.2 / 18 Si una unión de diseño estándar contiene dos o más estructuras de diseño estándar que comparten una secuencia inicial común, y si el objeto de unión de diseño estándar actualmente contiene una de estas estructuras de diseño estándar, se permite inspeccionar la estructura común parte inicial de cualquiera de ellos. Dos estructuras de diseño estándar comparten una secuencia inicial común si los miembros correspondientes tienen tipos compatibles con el diseño y ninguno de los miembros es un campo de bits o ambos son campos de bits con el mismo ancho para una secuencia de uno o más miembros iniciales.

Dado:

  • struct A { int a; };
  • struct B: A { char c; double d; };
  • struct C { int a; char c; char* z; };

Dentro de una union X { B b; C c; }; union X { B b; C c; }; puede acceder a xba , xbc y xca , xcc al mismo tiempo; sin embargo, acceder a xbd (respectivamente xcz ) es una violación del alias si el tipo almacenado actualmente no es B (respectivamente, no C ).

Nota: informalmente, la tipificación estructural es como asignar el tipo a una tupla de sus campos (aplanarlos).

Nota: char* está específicamente exento de esta regla, puede ver cualquier pieza de memoria a través de char* .

En su caso, sin la definición de Data no puedo decir si la regla de "lentes" podría ser violada, sin embargo, dado que usted es:

  • sobrescribir memoria con Data antes de acceder a través de Data*
  • no acceder a través de int* después

entonces cumple con la regla de por vida y, por lo tanto, no se produce ningún alias en lo que respecta al idioma.


Mientras la memoria se use para una sola cosa a la vez, es segura. Básicamente, utiliza los datos asignados como una union .

Si desea usar la memoria para instancias de clases y no solo estructuras simples de estilo C o tipos de datos, debe recordar hacer una colocación nueva para "asignar" los objetos, ya que esto realmente llamará al constructor del objeto. El destructor al que debe llamar explícitamente cuando haya terminado con el objeto, no puede delete .


Mientras los Data sigan siendo un POD, esto debería estar bien. De lo contrario, tendría que cambiar a una ubicación nueva.

Sin embargo, pondría una afirmación estática para que esto no cambie durante la refactorización posterior


No encuentro ningún error al reutilizar el espacio de memoria. Lo único que me importa es la referencia colgante. Reutilizando el espacio de memoria como has dicho, creo que no tiene ningún efecto en el programa.
Puedes continuar con tu programación. Pero siempre es preferible free() el espacio y luego asignarlo a otra variable.


Siempre que solo maneje los tipos "C", esto estaría bien. Pero tan pronto como use las clases de C ++, tendrá problemas con la inicialización adecuada. Si suponemos que los Data serían std::string por ejemplo, el código sería muy incorrecto.

El compilador realmente no puede mover la tienda a través de la llamada a printf , porque ese es un efecto secundario visible. El resultado tiene que ser como si los efectos secundarios se produjeran en el orden que prescribe el programa.