J'examine 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 les communications entre (potentiellement ) différentes machines sur différentes architectures. Il a écrit un commentaire qui dit quelque chose comme:
Nous utilisons normalement
new
etdelete
, mais ici j'utilisemalloc
etfree
. Cela est nécessaire car certains compilateurs remplissent les données différemment lorsquenew
est utilisé, ce qui entraîne des erreurs de transfert de données entre différentes plates-formes. Cela ne se produit pas avecmalloc
.
Cela ne correspond à rien de ce que je sais des questions standard new
vs malloc
.
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 de sizeof
?).
malloc & placement new vs. new est une question assez populaire mais ne parle que de new
en utilisant des constructeurs où malloc
ne le fait pas, ce qui n'est pas pertinent pour cela.
comment malloc comprend l'alignement? dit que la mémoire est garantie d'être correctement alignée avec new
ou malloc
, ce que j'avais pensé auparavant.
Je suppose qu'il a mal diagnostiqué son propre bug dans le passé et en a déduit que new
et malloc
donnent différentes quantités de remplissage, ce qui, je pense, n'est probablement pas vrai. Mais je ne trouve pas la réponse avec Google ou dans une question précédente.
Aidez-moi, StackOverflow, vous êtes mon seul espoir!
IIRC il y a un point difficile. malloc
est garanti pour retourner une adresse alignée pour tout type standard. ::operator new(n)
est uniquement garanti pour retourner une adresse alignée pour tout type standard pas plus grand que n, et si T
n'est pas un type de caractère alors new T[n]
est uniquement requis pour 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 derniers bits d'un pointeur pour stocker des indicateurs, ou sinon vous fier à l'adresse pour avoir plus d'alignement que nécessaire.
Cela n'affecte pas le remplissage de 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 pense l'auteur de ce commentaire des objets sur la pile ou dans les globaux, qu'ils soient "rembourrés comme 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 que le code dont il parle est plus qu'une simple différence entre malloc(sizeof(Foo) * n)
vs new Foo[n]
. C'est peut-être plus comme:
malloc((sizeof(int) + sizeof(char)) * n);
vs.
struct Foo { int a; char b; }
new Foo[n];
Autrement dit, peut-être qu'il est en disant "J'utilise malloc", mais signifie "J'emballe manuellement les données dans des emplacements non alignés au lieu d'utiliser une structure". En fait, malloc
n'est pas nécessaire pour emballer manuellement la structure, mais ne pas réaliser que c'est un degré de confusion moindre. Il est nécessaire de définir la disposition des données envoyées sur le fil. Différentes implémentations remplissent les données différemment lorsque struct est utilisé.
Votre collègue a peut-être pensé au cookie magique de new[]/delete[]
(Il s'agit des informations utilisées par l'implémentation lors de la suppression d'un tableau). Cependant, cela n'aurait pas été un problème si l'allocation commençant à l'adresse renvoyée par new[]
Avait été utilisée (par opposition à l'allocateur).
Emballage semble plus probable. Des 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, pensez également aux tableaux). Avec malloc, la position d'une structure pourrait être spécifiée et donc plus facilement portable à un ABI étranger. Ces variations sont normalement évitées en spécifiant l'alignement et le compactage des structures de transfert.
Je pense que tu as raison. Le remplissage est effectué par le compilateur et non par new
ou malloc
. Les considérations de remplissage s'appliqueraient même si vous déclariez un tableau ou une structure sans utiliser new
ou malloc
du tout. Dans tous les cas, même si je peux voir comment différentes implémentations de new
et malloc
pourraient 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.
La disposition d'un objet ne peut pas dépendre de son allocation à l'aide de malloc
ou new
. Ils retournent tous les deux le même type de pointeur, et lorsque vous passez ce pointeur à d'autres fonctions, ils ne savent pas comment l'objet a été alloué. sizeof *ptr
dépend uniquement de la déclaration de ptr
, et non de la façon dont elle a été affectée.
Ceci est ma conjecture sauvage d'où vient cette chose. Comme vous l'avez mentionné, le problème est lié à la transmission de données via MPI.
Personnellement, pour mes structures de données complexes que je veux envoyer/recevoir sur 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 peut être supérieure à la taille de ses membres et donc il faut é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> A
Par-dessus 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 indique la taille du tableau (ou mieux encore le stocker dans un conteneur). Ceci pourrait devenir la raison derrière un bogue. D'une manière ou d'une autre, cependant, cela n'a rien à voir avec new
vs malloc
comme indiqué dans ce fil.
En c ++:new
keyword est utilisé pour allouer certains octets particuliers de mémoire 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 (classe, struct, union, int, char etc ...) et seuls les octets de mémoire seront alloués, ce qui est requis 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);
Lorsque je veux contrôler la disposition de ma structure de données ancienne, avec les compilateurs MS Visual, 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 .
Cela 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), les données récupérées des deux côtés conviennent bien. Je n'ai donc jamais eu à jouer avec malloc en C++.
Au pire, j'aurais envisagé de surcharger le nouvel opérateur afin qu'il exécute des choses délicates, plutôt que d'utiliser malloc directement en C++.