Anurag Vohra
Este es un ejemplo de trabajo mínimo para el problema al que me enfrento en mi código real.
#include <iostream>
namespace Test1 {
static const std::string MSG1="Something really big message";
}
struct Person{
std::string name;
};
int main() {
auto p = (Person*)malloc(sizeof(Person));
p = new(p)Person();
p->name=Test1::MSG1;
std::cout << "name: "<< p->name << std::endl;
free(p);
std::cout << "done" << std::endl;
return 0;
}
Cuando lo compilo y lo ejecuto a través de Valgrindme da este error:
definitivamente perdido: 31 bytes en 1 bloque
Restricciones
- estoy obligado a usar
malloc
en el ejemplo anterior, como en mi código real, uso una biblioteca C en mi proyecto C++, que usa estomalloc
internamente. Así que no puedo alejarme demalloc
uso, ya que no lo hago explícitamente en ninguna parte de mi código. - necesito reasignar
std::string name
dePerson
una y otra vez en mi código.
463035818_no_es_un_número
Las piezas importantes de su código línea por línea…
Asigne memoria para un objeto Person:
auto p = (Person*)malloc(sizeof(Person));
Construya un objeto Person en esa memoria ya asignada llamando a su constructor:
p = new(p)Person();
Libera la memoria asignada a través de malloc:
free(p);
Llamar al constructor a través de la colocación new
crea un std::string
. Esa cadena se destruiría en el destructor, pero nunca se llama al destructor. free
no llama a los destructores (al igual que malloc
no llama a un constructor).
malloc
sólo asigna la memoria. La ubicación nueva solo construye el objeto en la memoria ya asignada. Por lo tanto, debe llamar al destructor antes de llamar free
. Este es el único caso que conozco donde es correcto y necesario llamar explícitamente a un destructor:
auto p = (Person*)malloc(sizeof(Person));
p = new(p)Person();
p->~Person();
free(p);
-
La discusión sobre los comentarios en el código siempre es un poco complicada, pero para mí, el único comentario en el código que esperaría es la razón para usar la ubicación nueva con malloc (asignador, ejercicio…)
– stefaanv
1 de marzo a las 10:36
-
Para hacerlo en realidad claro lo que se filtra: porque el
Person
destructor no está siendo llamado,Person::name
no se destruye, por lo que la memoria asignada porstd::string
no está siendo desasignado. Eso es:Person
se está liberando, pero cualquier recuerdo al que apunte no es.–Roger Lipscombe
1 de marzo a las 16:04
-
@AnsonSavage sin ninguna restricción, probablemente no usarían la asignación dinámica para algo que es básicamente un
std::string
que ya administra la memoria asignada dinámicamente– 463035818_no_es_un_número
2 de marzo a las 8:23
-
@AnsonSavage Creo que tanto valgrind como el desinfectante de direcciones darían un error con respecto a la falta de coincidencia entre
malloc
yoperator delete
si intentaste hacer eso.– Daniel Schepler
2 de marzo a las 16:27
-
@AnsonSavage:
new
puede agregar metadatos específicos antes del elemento asignado, diferente (a menudo una expansión o reemplazo) de los metadatosmalloc
es contrabando.delete
se basa en esos metadatos para realizar la limpieza correctamente. Sinew
ymalloc
no estar de acuerdo en el exacto estructura de metadatos, entoncesdelete
yfree
también, y sucederán cosas terribles cuando los mezcles.– ShadowRanger
3 de marzo a las 0:51
Debe llamar manualmente al destructor antes free(p);
:
p->~Person();
O std::destroy_at(p)
que es lo mismo.
Matthieu M.
Señalando el problema
En primer lugar, aclaremos cuál es exactamente el problema ilustrando el estado de la memoria después de cada declaración.
int main() {
auto p = (Person*)malloc(sizeof(Person));
// +---+ +-------+
// | p | -> | ~~~~~ |
// +---+ +-------+
p = new(p)Person();
// +---+ +-------+
// | p | -> | name |
// +---+ +-------+
p->name=Test1::MSG1;
// +---+ +-------+ +---...
// | p | -> | name | -> |Something...
// +---+ +-------+ +---...
free(p);
// +---+ +---...
// | p | |Something...
// +---+ +---...
return 0;
}
Como puedes ver, llamando free(p)
liberó la memoria asignada originalmente por malloc
pero no liberó la memoria asignada por p->name
cuando se le asignó.
Este es tu fuga.
Resolviendo el problema
Hay dos aspectos para tener una Person
objeto en el montón:
- Una asignación de memoria, manejada por
malloc
/free
aquí. - Inicializando y finalizando esa memoria, manejada por llamadas a constructores y destructores.
Le falta la llamada al destructor, por lo tanto, los recursos en poder de Person
se filtran Aquí es la memoria, pero si Person
mantuvo un bloqueo, podría tener un mutex bloqueado para siempre, etc., por lo tanto, es necesario ejecutar el destructor.
El enfoque de estilo C es llamar al destructor usted mismo:
int main() {
auto p = (Person*)malloc(sizeof(Person));
p = new(p) Person();
p->name = Test1::MSG1;
std::cout << "name: "<< p->name << "\n";
// Problem "fixed".
p->~Person();
free(p);
std::cout << "done" << "\n";
return 0;
}
Sin embargo, esto no es C ++ idiomático: es propenso a errores, etc.
El enfoque de C++ es usar RAI para asegurarse de que cuando p
sale fuera de alcance, todos sus recursos están debidamente dispuestos: el destructor de Person
es ejecutado y la memoria asignada para Person
mismo se libera.
En primer lugar, vamos a crear algunos ayudantes. usé el c
namespace ya que no se el nombre de la biblioteca de C que usas, pero te invito a ser mas especifico:
namespace c {
struct Disposer<T> {
void operator()(T* p) {
p->~T();
free(p);
}
};
template <typename T>
using UniquePointer<T> = std::unique_ptr<T, Disposer<T>>;
template <typename T, typename... Args>
UniquePointer<T> make_unique(T* t, Args&&... args) {
try {
new
} catch(...) {
free
throw;
}
return UniquePointer{t};
}
} // namespace c
Y con eso, podemos mejorar el ejemplo original:
int main() {
auto raw = (Person*) malloc(sizeof(Person));
auto p = c::make_unique(raw);
p->name = Test1::MSG1;
std::cout << "name: "<< p->name << "\n";
// No need to call the destructor or free ourselves, welcome to RAII.
std::cout << "done" << "\n";
return 0;
}
Nota: No utilice std::endl
usar '\n'
o "\n"
en cambio. std::endl
llamadas .flush()
además de poner un final de línea, que rara vez es lo que quieres, ralentiza las cosas.
jxh
Como se mencionó en otras respuestas, la fuente de la fuga es que el destructor del name
miembro de Person
no se llama. Normalmente se llamaría implícitamente cuando el destructor para Person
se llama. Sin embargo, Person
nunca se destruye. El recuerdo por el Person
la instancia simplemente se libera con free
.
Entonces, tal como tenía que invocar explícitamente al constructor con la ubicación new
después malloc
también necesita invocar explícitamente el destructor antes free
.
También puede considerar sobrecargar el new
y delete
operadores.
struct Person {
std::string name;
void * operator new (std::size_t sz) { return std::malloc(sz); }
void operator delete (void *p) { std::free(p); }
};
De esta manera, puedes usar new
y delete
normalmente, cuando debajo usarán malloc
y free
.
int main (void) {
auto p = new Person;
//...
delete p;
}
Y de esta manera, puede usar un puntero inteligente de forma más natural.
int main (void) {
auto p = std:make_unique<Person>();
//... unique pointer will delete automatically
}
Por supuesto, podrías haber usado unique_ptr
con un eliminador personalizado con sus llamadas explícitas a malloc
y free
pero habría sido mucho más engorroso, y su eliminador aún necesitaría saber para invocar explícitamente al destructor también.
Davislor
Como han mencionado otros, la memoria dinámica asignada por los miembros de Person
solo es liberado por el destructor ~Person
cual free()
no llama
Si tiene que usar esta función con una biblioteca que requiere alguna inicialización y limpieza que no sea la predeterminada, como aquí, un enfoque es definir un nuevo eliminador, para que lo usen los punteros inteligentes de la biblioteca estándar: Esto funcionará incluso con un bloque de memoria que no asignó usted mismo.
#include <memory>
#include <new> // std::bad_alloc
#include <stdlib.h>
#include <string>
struct Person{
std::string name;
};
struct PersonDeleterForSomeLib {
constexpr void operator()(Person* ptr) const noexcept {
ptr->~Person();
free(ptr);
}
};
Person* Person_factory() // Dummy for the foreign code.
{
Person* const p = static_cast<Person*>(malloc(sizeof(Person)));
if (!p) {
throw std::bad_alloc();
}
new(p) Person();
return p;
}
Esto le permite usar con seguridad:
const auto p =
std::unique_ptr<Person, PersonDeleterForSomeLib>(Person_factory());
con gestión automática de memoria. Puede devolver el puntero inteligente de la función, y tanto el destructor como free()
será llamado cuando termine su vida útil. También puede crear un std::shared_ptr
Por aquí. Si por alguna razón necesita destruir el objeto mientras el puntero inteligente todavía está activo, puede reset
o release
él.
-
-
@PeterMortensen Limpié ligeramente la redacción de eso (y qué memoria se está filtrando).
– Davislor
2 mar a las 18:00
-
-
@PeterMortensen Limpié ligeramente la redacción de eso (y qué memoria se está filtrando).
– Davislor
2 mar a las 18:00
Debes llamar al destructor antes
free
.– Santo Gato Negro
1 de marzo a las 7:16
Cuando realiza una ubicación nueva, debe llamar explícitamente al destructor de objetos. Al igual que
malloc
no construye objetos,free
no destruye objetos.– Un tipo programador
1 de marzo a las 7:16
Este es un ejemplo de trabajo mínimo — Te olvidaste
#include <string>
y#include <cstdlib>
– Paul McKenzie
1 de marzo a las 7:57
@PaulMcKenzie Sin embargo, es un descuido comprensible: algunos (aunque no todos) los compiladores/bibliotecas del mundo real tienen
<string>
y<cstdlib>
están incluidos por<iostream>
(la norma no lo exige ni lo impide).– Pedro
1 de marzo a las 9:29
no veo ninguna necesidad de
alignas
@PaulSanders, dado questd::malloc()
devuelve la memoria convenientemente alineado para que pueda ser asignado a un puntero a cualquier tipo de objeto con un requisito de alineación fundamental (o un puntero nulo, por supuesto).–Toby Speight
2 de marzo a las 8:20