2011-03-15 4 views
4

Possible en double:
C++: why is new needed?Quel est le problème avec malloc() et les fonctions virtuelles?

Pourquoi ne puis-je utiliser malloc pour allouer de l'espace pour mes objets quand ils sont les enfants d'une classe contenant des fonctions virtuelles? C'est vraiment frustrant. Y a-t-il une bonne raison?

Le programme suivant illustre le problème. Il segfaults sur la ligne 27, où j'appelle aa-> f()

#include <iostream> 
#include <cstdlib> 

class A 
{ 
public: 
    virtual int f() {return 1;} 
}; 

class B 
{ 
public: 
    int f() {return 1;} 
}; 

class Aa : public A {}; 

class Bb : public B {}; 

int main() 
{ 
    Aa* aa = (Aa*)malloc(sizeof(Aa)); 
    Aa* aan = (Aa*)new Aa(); 
    Bb* bb = (Bb*)malloc(sizeof(Bb)); 
    std::cout << bb->f() << std::endl; 
    std::cout << aan->f() << std::endl; 
    std::cout << aa->f() << std::endl; 
    return 0; 
} 

Version info: g ++ (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5

+1

Solution ici: http://stackoverflow.com/questions/4904762/c-why-is-new-needed/4904873#4904873 –

+0

@Tomalak: vous devriez voter pour fermer en double si vous le pouvez (je crois que 5k rep vous êtes autorisé à) –

+0

La réponse est simple. 'new' est la façon correcte de créer de nouvelles instances de classes en C++. 'malloc' n'est pas. –

Répondre

6

Une façon courante d'implémenter des fonctions virtuelles est d'avoir un pointeur vers une "table virtuelle" ou vtable avec un décalage négatif par rapport à l'objet. Cette table est nécessaire pour déterminer quelle fonction virtuelle appeler. C'est pourquoi juste l'espace de malloc'ing ne fonctionne pas.

+0

Ummm, oui. C'est ce que j'ai dit, n'est-ce pas? J'ai explicitement dit un pointeur vers une table virtuelle, pas la table virtuelle elle-même ... – DavidK

+0

J'ai mal lu votre réponse aussi. Je pense que l'anglais est ambigu. Il pourrait être lu (comme vous le vouliez) "... un pointeur vers une 'table virtuelle', ce pointeur est à un décalage négatif" ou il pourrait être mal interprété "un pointeur vers un vtable, lequel vtable est à un décalage négatif" . Vaut encore un +1. –

+0

J'ai mal lu, désolé :) –

2

Ne pas utiliser malloc, utilisez new - malloc n'appelle pas les constructeurs.

Lorsque vous faites A * a = new A(); le compilateur allouera la mémoire, configurer le pointeur vtable pour A et appelez le constructeur. Lorsque vous appelez une fonction virtuelle, la vtable est utilisée pour trouver la fonction.

Lorsque vous faites A * a = (A *) malloc(...); le compilateur va allouer de la mémoire, qui contiendra des données aléatoires. Lorsque vous appelez une fonction virtuelle, elle regarde la vtable (garbage) et appelle un emplacement aléatoire.

Une classe avec des fonctions virtuelles ressembler à quelque chose comme ça en interne:

struct Foo { 
    void * vtable; 
    int aClassMemberVar; 
}; 

Appel d'une fonction virtuelle regarde le « caché » pointeur vtable, qui pointe vers la classe vtable, une liste chaînée de pointeurs vers les fonctions. Donc, ce pointeur vtable doit être initialisé, et malloc ne le fait pas.

+2

ou créer le vtable, qui est l'élément pertinent ici. –

+0

Je pense qu'il est important de différencier la * table de méthode virtuelle * (unique par type) du * pointeur vtable * (par objet). Dans toutes les implémentations que je connais qui utilisent * vtable * s, la table existe indépendamment du fait que n'importe quel objet du type soit instancié. Le problème particulier ici est que le pointeur * vtable * dans cet objet particulier n'est pas initialisé, et quand vous essayez d'appeler n'importe quelle méthode virtuelle, la mémoire située à l'adresse aléatoire sera interprétée comme un * vtable *. –

+0

@Tomalak: nouveau ne crée pas le vtable. – Erik

6

malloc n'attribue que de la mémoire, mais ne crée pas d'objet. Ainsi, la ligne

Aa* aa = (Aa*)malloc(sizeof(Aa)); 

alloue une région de mémoire qui est assez grand pour contenir un A, mais contient des ordures. Comme d'autres l'ont souligné, cela signifie également que le pointeur sur le vtable ne sera pas défini (je l'ai trouvé dans le commentaire de @David Rodríguez sur une autre réponse), ce qui est nécessaire pour répartir les appels aux fonctions virtuelles. Puisque B ne contient pas de fonctions virtuelles, aucun problème de ce type ne se pose.Il se passerait-il avec B aussi, cependant, si B contenait des membres de données qui nécessitent l'initialisation, comme cela:

class B 
{ 
public: 
    B() : foo(new int()) {} 

    int f() {return *foo;} 
private: 
    int * foo; 
}; 

La ligne

Aa* aan = (Aa*)new Aa(); 

peut faire sans le casting:

Aa* aan = new Aa(); 
0

malloc n'appelle pas le constructeur de la classe, donc votre objet n'est pas initialisé correctement, d'où il segmente les erreurs. Utilisez new pour allouer de la mémoire lors de l'utilisation de C++. BTW, il n'est pas nécessaire de lancer le pointeur retourné de new.

1

Sûrement parce que la table de fonction virtuelle n'est pas créée correctement?

+0

Le pointeur vers la table ne sera pas défini, mais la table existera dans le système. –

+0

@David: c'est un peu mieux de dire la même chose! :) – Nick

6

La raison est que malloc ne sait rien sur les constantes C++ et par conséquent ne les appelle pas. Vous pouvez appeler les constuctors vous à l'aide placement new:

int main() 
{ 
    Aa* aa = (Aa*)malloc(sizeof(Aa)); 
    new(aa)Aa; 

    Aa* aan = (Aa*)new Aa(); 

    Bb* bb = (Bb*)malloc(sizeof(Bb)); 
    new(bb)Bb; 

    std::cout << bb->f() << std::endl; 
    std::cout << aan->f() << std::endl; 
    std::cout << aa->f() << std::endl; 

    aa->~Aa(); 
    free(aa); 

    delete aan; 

    bb->~Bb(); 
    free(bb); 

    return 0; 
} 

Notez que vous devez appeler manuellement avant de libérer les Destructeurs cette mémoire.

+0

+1 Quelqu'un m'a donné un +1 pour ma petite réponse de Placement Nouveau ... Je n'avais pas vu votre plus complet, donc je vous copie +1 :-) Je noterai que votre code est VRAIMENT illisible :-) :-) Quelques lignes vides ici pourraient aider. – xanatos

0

La bonne raison est appelée tables virtuelles. Les objets de types qui ont des méthodes virtuelles ont une table de pointeurs pointant vers l'adresse des méthodes virtuelles réelles à appeler. Ce sont les tables virtuelles ou les tables virtuelles.