Je vois qu'en C++, il existe plusieurs façons d'allouer et de libérer des données et je comprends que lorsque vous appelez malloc
, vous devez appeler free
et lorsque vous utilisez l'opérateur new
, vous devez vous associer à delete
et c'est une erreur de mélanger les deux (par exemple, Appeler free()
sur quelque chose qui a été créé avec l'opérateur new
, mais je ne suis pas sûr du moment d'utiliser malloc
/free
et quand je devrais utiliser new
/delete
dans mes programmes réels.
Si vous êtes un expert en C++, indiquez-moi les règles de base ou les conventions que vous suivez à cet égard.
Sauf si vous êtes obligé d'utiliser C, vous devriez ne jamais utilisermalloc
. Toujours utiliser new
.
Si vous avez besoin d’une grande quantité de données, procédez comme suit:
char *pBuffer = new char[1024];
Attention, ce n'est pas correct:
//This is incorrect - may delete only one element, may corrupt the heap, or worse...
delete pBuffer;
Au lieu de cela, vous devriez le faire lors de la suppression d'un tableau de données:
//This deletes all items in the array
delete[] pBuffer;
Le mot clé new
est le moyen de le faire en C++, et il garantira que votre type aura son constructeur appelé. Le mot clé new
est également plus sécurité du type alors que malloc
n'est pas du tout sûr.
La seule façon dont je pourrais penser qu'il serait avantageux d'utiliser malloc
serait si vous aviez besoin de changer la taille de votre tampon de données. Le mot clé new
n'a pas de manière analogue à realloc
. La fonction realloc
pourrait peut-être étendre plus efficacement la taille d’une partie de la mémoire.
Il est à noter que vous ne pouvez pas mélanger new
/free
et malloc
/delete
.
Remarque: Certaines réponses à cette question ne sont pas valides.
int* p_scalar = new int(5); // Does not create 5 elements, but initializes to 5
int* p_array = new int[5]; // Creates 5 elements
La réponse courte est: n'utilisez pas malloc
pour C++ sans une très bonne raison de le faire. malloc
présente un certain nombre d'inconvénients lorsqu'il est utilisé avec C++, lequel new
a été défini pour être résolu.
malloc
n'est pas typé de manière significative. En C++, vous devez convertir le résultat de void*
. Cela pose potentiellement beaucoup de problèmes:
#include <stdlib.h>
struct foo {
double d[5];
};
int main() {
foo *f1 = malloc(1); // error, no cast
foo *f2 = static_cast<foo*>(malloc(sizeof(foo)));
foo *f3 = static_cast<foo*>(malloc(1)); // No error, bad
}
C'est pire que ça cependant. Si le type en question est POD (plain old data) , vous pouvez utiliser de manière semi-sensée malloc
pour lui allouer de la mémoire, comme le fait f2
dans le premier exemple.
Ce n'est pas si évident cependant si un type est POD. Le fait qu'il soit possible pour un type donné de passer d'un POD à un autre, sans qu'il en résulte une erreur du compilateur et un problème potentiellement très difficile à résoudre, est un facteur important. Par exemple, si quelqu'un (peut-être un autre programmeur, beaucoup plus tard lors de la maintenance, apportait une modification qui empêchait foo
de devenir POD, aucune erreur évidente ne se produirait au moment de la compilation, comme vous le souhaitiez, par exemple:
struct foo {
double d[5];
virtual ~foo() { }
};
rendrait la malloc
de f2
également mauvaise, sans aucun diagnostic évident. L'exemple présenté ici est trivial, mais il est possible d'introduire accidentellement un non-PODness beaucoup plus loin (par exemple, dans une classe de base, en ajoutant un membre non-POD). Si vous avez C++ 11/boost, vous pouvez utiliser is_pod
pour vérifier que cette hypothèse est correcte et produire une erreur si ce n'est pas le cas:
#include <type_traits>
#include <stdlib.h>
foo *safe_foo_malloc() {
static_assert(std::is_pod<foo>::value, "foo must be POD");
return static_cast<foo*>(malloc(sizeof(foo)));
}
Bien que boost soit incapable de déterminer si un type est POD sans C++ 11 ou d'autres extensions du compilateur.
malloc
renvoie NULL
si l'attribution échoue. new
lancera std::bad_alloc
. Le comportement de l'utilisation ultérieure d'un pointeur NULL
n'est pas défini. Une exception a une sémantique propre lorsqu'elle est renvoyée et elle est renvoyée de la source de l'erreur. Envelopper malloc
avec un test approprié à chaque appel semble fastidieux et sujet aux erreurs. (Il suffit d'oublier une fois pour annuler tout ce bon travail). Une exception peut être autorisée à se propager à un niveau où un appelant est capable de la traiter de manière judicieuse, où NULL
est beaucoup plus difficile à renvoyer de manière significative. Nous pourrions étendre notre fonction safe_foo_malloc
pour générer une exception, quitter le programme ou appeler un gestionnaire:
#include <type_traits>
#include <stdlib.h>
void my_malloc_failed_handler();
foo *safe_foo_malloc() {
static_assert(std::is_pod<foo>::value, "foo must be POD");
foo *mem = static_cast<foo*>(malloc(sizeof(foo)));
if (!mem) {
my_malloc_failed_handler();
// or throw ...
}
return mem;
}
Fondamentalement malloc
est une fonctionnalité C et new
est une fonctionnalité C++. En conséquence, malloc
ne fonctionne pas correctement avec les constructeurs. Il se contente d’allouer un bloc d’octets. Nous pourrions étendre notre safe_foo_malloc
pour continuer à utiliser le placement new
:
#include <stdlib.h>
#include <new>
void my_malloc_failed_handler();
foo *safe_foo_malloc() {
void *mem = malloc(sizeof(foo));
if (!mem) {
my_malloc_failed_handler();
// or throw ...
}
return new (mem)foo();
}
Notre fonction safe_foo_malloc
n'est pas très générique - idéalement, nous voudrions quelque chose qui puisse gérer tout type, pas seulement foo
. Nous pouvons y parvenir avec des modèles et des modèles variadiques pour les constructeurs autres que ceux par défaut:
#include <functional>
#include <new>
#include <stdlib.h>
void my_malloc_failed_handler();
template <typename T>
struct alloc {
template <typename ...Args>
static T *safe_malloc(Args&&... args) {
void *mem = malloc(sizeof(T));
if (!mem) {
my_malloc_failed_handler();
// or throw ...
}
return new (mem)T(std::forward(args)...);
}
};
Maintenant, nous avons pratiquement réinventé l’opérateur par défaut new
en résolvant tous les problèmes que nous avions identifiés jusqu’à présent. Si vous utilisez malloc
et le placement new
, vous pouvez aussi bien utiliser new
pour commencer!
De la C++ FQA Lite :
[16.4] Pourquoi devrais-je utiliser un nouveau malloc () digne de confiance?
FAQ: new/delete appelle le constructeur/destructeur; new est de type sûr, pas de malloc; new peut être remplacé par une classe.
FQA: Les vertus de new mentionnées par la FAQ ne sont pas des vertus, car les constructeurs, les destructeurs et la surcharge d'opérateurs sont sans intérêt (voyez ce qui se passe lorsque vous n'avez pas de récupération de place?) minuscule ici (normalement, vous devez convertir le vide * renvoyé par malloc vers le type de pointeur correct pour l'attribuer à une variable pointée typée, ce qui peut être ennuyeux, mais loin d'être "dangereux").
Oh, et utiliser old malloc digne de confiance permet d’utiliser le realloc tout aussi digne de confiance. Dommage que nous n’ayons pas de nouvel opérateur brillant à renouveler ou quelque chose du genre.
Quoi qu'il en soit, le nouveau n'est pas suffisant pour justifier un écart par rapport au style commun utilisé dans un langage, même lorsque le langage est C++. En particulier, les classes avec des constructeurs non-triviaux se comporteront de manière fatale si vous simplement mallociez les objets. Alors pourquoi ne pas utiliser new dans tout le code? Les gens surchargent rarement les nouveaux opérateurs, donc ils ne vous gêneront probablement pas trop. Et s’ils surchargent de nouveau, vous pouvez toujours leur demander de s’arrêter.
Désolé, je n'ai pas pu résister. :)
Toujours utiliser new en C++. Si vous avez besoin d'un bloc de mémoire non typé, vous pouvez utiliser directement l'opérateur new:
void *p = operator new(size);
...
operator delete(p);
Utilisez malloc
et free
uniquement pour allouer de la mémoire qui sera gérée par c-centric bibliothèques et API. Utilisez new
et delete
(et le []
variantes) pour tout ce que vous contrôlez.
new vs malloc ()
1) new
est un opérateur, alors que malloc()
est un fonction.
2) new
appelle constructeurs, alors que malloc()
ne le fait pas.
3) new
renvoie type de données exact, alors que malloc()
renvoie void *.
4) new
ne retourne jamais un NULL (lancera en cas d'échec) tandis que malloc()
retournera NULL
5) Réallocation de mémoire non gérée par new
alors que malloc()
peut
Pour répondre à votre question, vous devez savoir la différence entre malloc
et new
. La différence est simple:
malloc
alloue de la mémoire, tandis que new
alloue de la mémoire ET appelle le constructeur de l'objet pour lequel vous allouez de la mémoire.
Donc, à moins que vous ne soyez limité au C, vous ne devriez jamais utiliser malloc, en particulier lorsqu'il s'agit d'objets C++. Ce serait une recette pour briser votre programme.
De plus, la différence entre free
et delete
est pratiquement la même. La différence est que delete
appellera le destructeur de votre objet en plus de libérer de la mémoire.
Il y a une grande différence entre malloc
et new
. malloc
alloue de la mémoire. C'est bien pour C, car dans C, une masse de mémoire est un objet.
En C++, si vous n'avez pas affaire à des types POD (semblables aux types C), vous devez appeler un constructeur sur un emplacement mémoire pour y placer un objet. Les types non-POD sont très courants en C++, car de nombreuses fonctionnalités C++ rendent automatiquement un objet non-POD.
new
alloue de la mémoire et crée un objet sur cet emplacement de la mémoire. Pour les types non-POD, cela signifie qu’il faut appeler un constructeur.
Si vous faites quelque chose comme ça:
non_pod_type* p = (non_pod_type*) malloc(sizeof *p);
Le pointeur que vous obtenez ne peut pas être déréférencé car il ne pointe pas vers un objet. Vous devez appeler un constructeur dessus avant de pouvoir l'utiliser (et cela se fait en utilisant le placement new
).
Si, par contre, vous faites:
non_pod_type* p = new non_pod_type();
Vous obtenez un pointeur qui est toujours valide, car new
a créé un objet.
Même pour les types de POD, il existe une différence significative entre les deux:
pod_type* p = (pod_type*) malloc(sizeof *p);
std::cout << p->foo;
Ce morceau de code afficherait une valeur non spécifiée, car les objets POD créés par malloc
ne sont pas initialisés.
Avec new
, vous pouvez spécifier un constructeur à appeler et ainsi obtenir une valeur bien définie.
pod_type* p = new pod_type();
std::cout << p->foo; // prints 0
Si vous le souhaitez vraiment, vous pouvez utiliser new
pour obtenir des objets POD non initialisés. Voir cette autre réponse pour plus d'informations à ce sujet.
Une autre différence est le comportement en cas d'échec. Lorsqu'il n'alloue pas de mémoire, malloc
renvoie un pointeur nul, tandis que new
lève une exception.
Dans le premier cas, vous devez tester chaque pointeur renvoyé avant de l’utiliser, tandis que le dernier produira toujours des pointeurs valides.
Pour ces raisons, dans le code C++, vous devez utiliser new
et non pas malloc
. Mais même dans ce cas, vous ne devriez pas utiliser new
"à l’air libre", car il acquiert les ressources que vous devez libérer ultérieurement. Lorsque vous utilisez new
, vous devez immédiatement transmettre son résultat à une classe de gestion de ressources:
std::unique_ptr<T> p = std::unique_ptr<T>(new T()); // this won't leak
new
fait que malloc
ne fait pas:
new
construit l'objet en appelant le constructeur de cet objetnew
ne nécessite pas de conversion de type de la mémoire allouée.Donc, si vous utilisez malloc
, vous devez faire les choses ci-dessus explicitement, ce qui n’est pas toujours pratique. De plus, new
peut être surchargé mais malloc
ne peut pas l'être.
Si vous travaillez avec des données qui ne nécessitent pas de construction/destruction et qui nécessitent des réaffectations (par exemple, un grand nombre d’ints), alors je pense que malloc/free est un bon choix car il vous donne realloc, ce qui est bien plus rapide que new-memcpy. -delete (c'est sur ma machine Linux, mais je suppose que cela dépend de la plate-forme). Si vous travaillez avec des objets C++ qui ne sont pas POD et nécessitent une construction/destruction, vous devez utiliser les opérateurs new et delete.
Quoi qu'il en soit, je ne vois pas pourquoi vous ne devriez pas utiliser les deux (à condition que vous libériez votre mémoire mallocée et que vous supprimiez les objets alloués avec new) si vous pouvez tirer parti de l'accélération de la vitesse (parfois importante si vous réaffectez des tableaux de grande taille de POD) que realloc peut vous donner.
Sauf si vous en avez besoin, vous devriez vous en tenir à nouveau/supprimer en C++.
Si vous utilisez C++, essayez d'utiliser new/delete au lieu de malloc/calloc car ils sont des opérateurs. Pour malloc/calloc, vous devez inclure un autre en-tête. Ne mélangez pas deux langues différentes dans le même code. Leur travail est semblable à tous égards, les deux allouent de la mémoire dynamiquement à partir du segment de tas dans la table de hachage.
L'allocation dynamique n'est requise que lorsque la durée de vie de l'objet doit être différente de la portée dans laquelle il est créé (cela s'applique également à la réduction de la portée plus grande) et vous avez une raison spécifique pour laquelle le stockage par valeur ne le permet pas. travail.
Par exemple:
std::vector<int> *createVector(); // Bad
std::vector<int> createVector(); // Good
auto v = new std::vector<int>(); // Bad
auto result = calculate(/*optional output = */ v);
auto v = std::vector<int>(); // Good
auto result = calculate(/*optional output = */ &v);
À partir de C++ 11, nous avons std::unique_ptr
pour traiter la mémoire allouée, qui contient la propriété de la mémoire allouée. std::shared_ptr
a été créé pour le partage de la propriété. (vous aurez besoin de moins que ce que vous attendez dans un bon programme)
Créer une instance devient vraiment facile:
auto instance = std::make_unique<Class>(/*args*/); // C++14
auto instance = std::make_unique<Class>(new Class(/*args*/)); // C++11
auto instance = std::make_unique<Class[]>(42); // C++14
auto instance = std::make_unique<Class[]>(new Class[](42)); // C++11
C++ 17 ajoute également std::optional
, ce qui peut vous éviter de demander des allocations de mémoire.
auto optInstance = std::optional<Class>{};
if (condition)
optInstance = Class{};
Dès que "l'instance" est hors de portée, la mémoire est nettoyée. Le transfert de propriété est également facile:
auto vector = std::vector<std::unique_ptr<Interface>>{};
auto instance = std::make_unique<Class>();
vector.Push_back(std::move(instance)); // std::move -> transfer (most of the time)
Alors, quand avez-vous encore besoin de new
? Presque jamais à partir de C++ 11. La plupart d’entre vous utilisez std::make_unique
jusqu’à atteindre une API qui transfère la propriété via des pointeurs bruts.
auto instance = std::make_unique<Class>();
legacyFunction(instance.release()); // Ownership being transferred
auto instance = std::unique_ptr<Class>{legacyFunction()}; // Ownership being captured in unique_ptr
En C++ 98/03, vous devez gérer manuellement la mémoire. Si vous êtes dans ce cas, essayez de mettre à niveau vers une version plus récente de la norme. Si vous êtes bloqué:
auto instance = new Class(); // Allocate memory
delete instance; // Deallocate
auto instances = new Class[42](); // Allocate memory
delete[] instances; // Deallocate
Assurez-vous de suivre correctement le propriétaire pour ne pas avoir de fuite de mémoire! La sémantique de déplacement ne fonctionne pas encore non plus.
Alors, quand avons-nous besoin de malloc en C++? La seule raison valable serait d'allouer de la mémoire et de l'initialiser ultérieurement via un nouveau placement.
auto instanceBlob = std::malloc(sizeof(Class)); // Allocate memory
auto instance = new(instanceBlob)Class{}; // Initialize via constructor
instance.~Class(); // Destroy via destructor
std::free(instanceBlob); // Deallocate the memory
Même si ce qui précède est valable, vous pouvez également le faire via un nouvel opérateur. std::vector
en est un bon exemple.
Enfin, nous avons toujours l’éléphant dans la pièce: C
. Si vous devez travailler avec une bibliothèque C où la mémoire est allouée dans le code C++ et libérée dans le code C (ou inversement), vous devez utiliser malloc/free.
Si vous êtes dans ce cas, oubliez les fonctions virtuelles, les fonctions membres, les classes ... Seules les structures contenant des POD sont autorisées.
Quelques exceptions aux règles:
Si vous souhaitez transférer du code C en C++, vous pouvez y laisser des appels à malloc (). Pour tout nouveau code C++, je vous recommanderais d'utiliser new à la place.
new
initialisera les valeurs par défaut de la structure et liera correctement les références qu'il contient à elle-même.
Par exemple.
struct test_s {
int some_strange_name = 1;
int &easy = some_strange_name;
}
Donc, new struct test_s
renverra une structure initialisée avec une référence de travail, alors que la version mallocée n'a pas de valeur par défaut et que les références internes ne sont pas initialisées.
Dans une perspective inférieure, new initialisera toute la mémoire avant de la donner, tandis que malloc conservera le contenu original de la mémoire.
Il est rare de penser à utiliser malloc/free au lieu de new/delete lorsque vous allouez puis réaffectez (types de pod simples, pas d'objets) à l'aide de realloc car il n'y a pas de fonction similaire à realloc en C++ (bien que cela puisse être fait avec approche plus C++).
Les opérateurs new
et delete
peuvent opérer sur des classes et des structures, alors que malloc
et free
ne fonctionnent qu'avec des blocs de mémoire devant être convertis.
L'utilisation de new/delete
aidera à améliorer votre code car vous n'aurez pas besoin de convertir la mémoire allouée dans la structure de données requise.