Malloc vs new - rembourrage différent

110

Je passe en revue le code C ++ de quelqu'un d'autre pour notre projet qui utilise MPI pour le calcul haute performance (10 ^ 5 - 10 ^ 6 cœurs). Le code est destiné à permettre des communications entre (potentiellement) différentes machines sur différentes architectures. Il a écrit un commentaire qui dit quelque chose du genre:

Nous utiliserions normalement newet delete, mais ici, j'utilise mallocet free. Cela est nécessaire car certains compilateurs rempliront les données différemment lorsqu'ils newsont utilisés, ce qui entraîne des erreurs de transfert de données entre différentes plates-formes. Cela n'arrive pas avec malloc.

Cela ne correspond à rien de ce que je sais des questions standard par newrapport aux mallocquestions.

Quelle est la différence entre new / delete et malloc / free? fait allusion à l'idée que le compilateur pourrait calculer la taille d'un objet différemment (mais alors pourquoi est-ce différent de l'utilisation sizeof?).

malloc & placement nouveau vs nouveau est une question assez populaire, mais ne parle que de l' newutilisation de constructeurs là où ce mallocn'est pas le cas, ce qui n'est pas pertinent pour cela.

comment malloc comprend-il l'alignement? dit que la mémoire est garantie d'être correctement alignée avec l'un newou l' autre ou mallocce que je pensais auparavant.

Je suppose qu'il a mal diagnostiqué son propre bug quelque temps dans le passé et en a déduit newet a mallocdonné différentes quantités de rembourrage, ce qui n'est probablement pas vrai. Mais je ne trouve pas la réponse avec Google ou dans aucune question précédente.

Aidez-moi, StackOverflow, vous êtes mon seul espoir!

hcarver
la source
33
+1 pour la recherche de différents fils SO uniquement!
iammilind
7
+1 Facilement l'un des meilleurs travaux de recherche «Aide-moi-même-avant-de-demander-aux-autres» que j'ai vu sur SO depuis LONGTEMPS. J'aimerais pouvoir voter quelques fois de plus.
WhozCraig
1
Le code de transfert suppose-t-il que les données sont alignées d'une manière spécifique, par exemple qu'elles commencent à une limite de huit octets? Cela peut différer entre mallocet new, comme newdans certains environnements, allouer un bloc, ajoute des données au début et renvoie un pointeur vers un emplacement juste après ces données. (Je suis d'accord avec les autres, à l'intérieur du bloc de données, mallocet je newdois utiliser le même type de rembourrage.)
Lindydancer
1
Wow, je ne m'attendais pas à ce que cette question soit aussi populaire! @Lindydancer, je ne pense pas qu'une limite de 8 octets soit supposée. Point intéressant cependant.
hcarver
1
Une des raisons d'utiliser une méthode d'allocation plutôt qu'une autre est que "quelqu'un d'autre" effectue la libération de l'objet. Si ce "quelqu'un d'autre" supprime l'objet en utilisant free, vous devez allouer en utilisant malloc. (Un problème de pad est un hareng rouge.)
Lindydancer

Réponses:

25

IIRC, il y a un point difficile. mallocest garanti de renvoyer une adresse alignée pour tout type standard. ::operator new(n)est uniquement garanti de renvoyer une adresse alignée pour tout type standard ne dépassant pas n , et si ce Tn'est pas un type de caractère, alors il new T[n]est seulement nécessaire de renvoyer une adresse alignée pour T.

Mais cela n'est pertinent que lorsque vous jouez à des astuces spécifiques à l'implémentation, comme utiliser les quelques bits inférieurs d'un pointeur pour stocker des indicateurs, ou si vous comptez sur l'adresse pour avoir plus d'alignement que ce dont elle a strictement besoin.

Cela n'affecte pas le remplissage dans l'objet, qui a nécessairement exactement la même disposition, quelle que soit la façon dont vous avez alloué la mémoire qu'il occupe. Il est donc difficile de voir comment la différence pourrait entraîner des erreurs de transfert de données.

Y a-t-il un signe de ce que l'auteur de ce commentaire pense des objets sur la pile ou dans les globaux, qu'ils soient à son avis "rembourrés comme du malloc" ou "rembourrés comme neufs"? Cela pourrait donner des indices sur l'origine de l'idée.

Peut-être qu'il est confus, mais peut - être le code dont il parle est plus qu'une différence entre droite malloc(sizeof(Foo) * n)vs new Foo[n]. Peut-être que c'est plus comme:

malloc((sizeof(int) + sizeof(char)) * n);

contre.

struct Foo { int a; char b; }
new Foo[n];

Autrement dit, il dit peut-être "J'utilise malloc", mais signifie "Je compresse manuellement les données dans des emplacements non alignés au lieu d'utiliser une structure". En fait, ce mallocn'est pas nécessaire pour emballer manuellement la structure, mais ne pas s'en rendre compte est un moindre degré de confusion. Il est nécessaire de définir la disposition des données envoyées sur le fil. Différentes implémentations rempliront les données différemment lorsque la structure est utilisée.

Steve Jessop
la source
Merci pour les points concernant l'alignement. Les données en question sont un tableau de caractères, donc je soupçonne que ce n'est pas une chose d'alignement ici, ni une chose de struct - même si c'était aussi ma première pensée.
hcarver
5
@Hbcdev: les chartableaux ne sont jamais bourrés du tout, donc je m'en tiendrai à "confused" comme explication.
Steve Jessop
5

Votre collègue a peut-être new[]/delete[]pensé au cookie magique (ce sont les informations que l'implémentation utilise lors de la suppression d'un tableau). Cependant, cela n'aurait pas posé de problème si l'allocation commençant à l'adresse renvoyée par new[]était utilisée (par opposition à celle de l'allocateur).

L'emballage semble plus probable. Les variations dans les ABI pourraient (par exemple) entraîner un nombre différent d'octets de fin ajoutés à la fin d'une structure (ceci est influencé par l'alignement, considérez également les tableaux). Avec malloc, la position d'une structure pourrait être spécifiée et donc plus facilement transférable à un ABI étranger. Ces variations sont normalement évitées en spécifiant l'alignement et le tassement des structures de transfert.

Justin
la source
2
C'est ce que j'ai d'abord pensé, le problème "struct est plus grand que la somme de ses parties". C'est peut-être de là que vient son idée.
hcarver
3

La disposition d'un objet ne peut pas dépendre du fait qu'il a été alloué à l'aide de mallocou new. Ils renvoient tous les deux le même type de pointeur, et lorsque vous passez ce pointeur à d'autres fonctions, ils ne sauront pas comment l'objet a été alloué. sizeof *ptrdépend simplement de la déclaration ptr, et non de la manière dont il a été attribué.

Barmar
la source
3

Je pense que tu as raison. Le remplissage est effectué par le compilateur non newou malloc. Les considérations de remplissage s'appliqueraient même si vous déclariez un tableau ou une structure sans utiliser newou pas mallocdu tout. Dans tous les cas, bien que je puisse voir comment différentes implémentations de newet mallocpourraient causer des problèmes lors du portage de code entre plates-formes, je ne vois absolument pas comment elles pourraient causer des problèmes de transfert de données entre plates-formes.

John
la source
J'avais précédemment supposé que vous pouviez le considérer newcomme un bon emballage pour, mallocmais il semble que d'autres réponses ne soient pas tout à fait vraies. Le consensus semble être que le rembourrage devrait être le même avec l'un ou l'autre; Je pense que le problème du transfert de données entre plates-formes ne survient que si votre mécanisme de transfert est défectueux :)
hcarver
0

Lorsque je veux contrôler la disposition de ma structure de données ancienne, avec les compilateurs MS Visual que j'utilise #pragma pack(1). Je suppose qu'une telle directive de précompilateur est prise en charge pour la plupart des compilateurs, comme par exemple gcc .

Ceci a pour conséquence d'aligner tous les champs des structures les uns derrière les autres, sans espaces vides.

Si la plate-forme à l'autre extrémité fait de même (c'est-à-dire a compilé sa structure d'échange de données avec un remplissage de 1), alors les données récupérées des deux côtés conviennent parfaitement. Ainsi je n'ai jamais eu à jouer avec malloc en C ++.

Au pire, j'aurais envisagé de surcharger le nouvel opérateur afin qu'il effectue des choses délicates, plutôt que d'utiliser malloc directement en C ++.

Stéphane Rolland
la source
Dans quelles situations souhaitez-vous contrôler la disposition de la structure de données? Juste curieux.
hcarver
Et est-ce que quelqu'un connaît des compilateurs prenant en charge pragma packou similaires? Je sais que cela ne fera pas partie de la norme.
hcarver
gcc le prend en charge par exemple. dans quelle situation ai-je besoin de cela: partager des données binaires entre deux plates-formes différentes: partager un flux binaire entre Windows et palmOS, entre Windows et Linux. liens sur gcc: gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
Stephane Rolland
0

C'est ma supposition sauvage d'où vient cette chose. Comme vous l'avez mentionné, le problème vient de la transmission de données via MPI.

Personnellement, pour mes structures de données complexes que je veux envoyer / recevoir via MPI, j'implémente toujours des méthodes de sérialisation / désérialisation qui emballent / décompressent le tout dans / à partir d'un tableau de caractères. Maintenant, en raison du remplissage, nous savons que cette taille de la structure pourrait être plus grande que la taille de ses membres et il faut donc également calculer la taille non rembourrée de la structure de données afin de savoir combien d'octets sont envoyés / reçus.

Par exemple, si vous souhaitez envoyer / recevoir std::vector<Foo> Avia MPI avec ladite technique, il est faux de supposer que la taille du tableau de caractères résultant est A.size()*sizeof(Foo)en général. En d'autres termes, chaque classe qui implémente des méthodes de sérialisation / désérialisation, doit également implémenter une méthode qui rapporte la taille du tableau (ou mieux encore stocker le tableau dans un conteneur). Cela pourrait devenir la raison d'un bug. D'une manière ou d'une autre, cependant, cela n'a rien à voir avec newvs malloccomme indiqué dans ce fil.

mmirzadeh
la source
La copie dans des tableaux de caractères peut être problématique - il est possible que certains de vos cœurs soient sur des architectures little-endian, et certains big-endian (peut-être pas probable, mais possible). Vous auriez à les encoder XDR ou quelque chose du genre, mais vous pourriez simplement utiliser des types de données MPI définis par l'utilisateur. Ils tiennent facilement compte du rembourrage. Mais je peux voir ce que vous dites à propos de la cause possible d'un malentendu - c'est ce que j'appelle le problème "la structure est plus grande que la somme de ses parties".
hcarver
Oui, la définition des types de données MPI est une autre manière correcte de procéder. Bon point sur l'endianness. Cependant, je doute vraiment que cela se produise sur des grappes réelles. Quoi qu'il en soit, je pensais que s'ils suivaient la même stratégie, cela pourrait conduire à des bugs ...
mmirzadeh
0

En c ++: le new mot clé est utilisé pour allouer des octets de mémoire particuliers par rapport à une structure de données. Par exemple, vous avez défini une classe ou une structure et vous souhaitez allouer de la mémoire à son objet.

myclass *my = new myclass();

ou

int *i = new int(2);

Mais dans tous les cas, vous avez besoin du type de données défini (class, struct, union, int, char etc ...) et seuls les octets de mémoire seront alloués, ce qui est nécessaire pour son objet / variable. (c'est-à-dire des multiples de ce type de données).

Mais dans le cas de la méthode malloc (), vous pouvez allouer n'importe quel octet de mémoire et vous n'avez pas besoin de spécifier le type de données à tout moment. Ici vous pouvez l'observer dans quelques possibilités de malloc ():

void *v = malloc(23);

ou

void *x = malloc(sizeof(int) * 23);

ou

char *c = (char*)malloc(sizeof(char)*35);
Rahul Raina
la source
-1

malloc est un type de fonction et new est un type de type de données en c ++ en c ++, si nous utilisons malloc que nous devons et devrions utiliser typecast sinon le compilateur vous donnera une erreur et si nous utilisons un nouveau type de données pour l'allocation de mémoire que nous n'avons pas besoin pour typer

hk_043
la source
1
Je pense que vous devriez essayer d'argumenter un peu plus votre réponse.
Carlo
Cela ne semble pas répondre à la question de savoir s'ils font des choses différentes avec des rembourrages, ce que je demandais vraiment ci-dessus.
hcarver