Malloc vs nuovo - imbottitura diversa


110

Sto esaminando il codice C ++ di qualcun altro per il nostro progetto che utilizza MPI per il calcolo ad alte prestazioni (10 ^ 5 - 10 ^ 6 core). Il codice ha lo scopo di consentire comunicazioni tra macchine (potenzialmente) differenti su architetture differenti. Ha scritto un commento che dice qualcosa sulla falsariga di:

Normalmente useremmo newe delete, ma qui sto usando malloce free. Ciò è necessario perché alcuni compilatori riempiranno i dati in modo diverso quando newvengono utilizzati, causando errori nel trasferimento dei dati tra piattaforme diverse. Questo non accade con malloc.

Questo non si adatta a tutto ciò che so da domande standard newvs.malloc

Qual è la differenza tra new / delete e malloc / free? suggerisce l'idea che il compilatore possa calcolare la dimensione di un oggetto in modo diverso (ma allora perché differisce dall'uso sizeof?).

malloc e posizionamento nuovo contro nuovo è una domanda abbastanza popolare, ma parla solo newdell'uso di costruttori dove mallocno, il che non è rilevante per questo.

come fa malloc a comprendere l'allineamento? dice che la memoria è garantita per essere correttamente allineata con newo mallocche è ciò che avevo pensato in precedenza.

La mia ipotesi è che abbia diagnosticato erroneamente il suo bug qualche tempo fa e lo abbia dedotto newe abbia mallocfornito diverse quantità di imbottitura, che penso probabilmente non sia vero. Ma non riesco a trovare la risposta con Google o in nessuna domanda precedente.

Aiutami, StackOverflow, sei la mia unica speranza!


33
+1 per la ricerca di vari thread SO da soli!
iammilind

7
+1 Facilmente uno dei migliori lavori di ricerca "aiuto-me stesso-prima-che-chiedo-agli-altri" che abbia visto su SO da molto tempo. Vorrei poter votare questo ancora un paio di volte.
WhozCraig

1
Il codice di trasferimento presume che i dati siano allineati in un modo specifico, ad esempio che iniziano con un confine di otto byte? Questo potrebbe differire tra malloce new, poiché newin alcuni ambienti alloca un blocco, aggiunge alcuni dati all'inizio e restituisce un puntatore a una posizione subito dopo questi dati. (Sono d'accordo con gli altri, all'interno del blocco dati, malloce newdevo usare lo stesso tipo di imbottitura.)
Lindydancer

1
Wow, non mi aspettavo che questa domanda fosse così popolare! @ Lindydancer, non credo che venga assunto alcun limite di 8 byte. Punto interessante però.
hcarver

1
Un motivo per utilizzare un metodo di allocazione rispetto a un altro è quando "qualcun altro" sta effettuando il rilascio dell'oggetto. Se questo "qualcun altro" elimina l'oggetto utilizzando free, è necessario allocare utilizzando malloc. (E il problema del pad è una
falsa pista

Risposte:


25

IIRC c'è un punto esigente. mallocè garantito per restituire un indirizzo allineato per qualsiasi tipo standard. ::operator new(n)è garantito solo per restituire un indirizzo allineato per qualsiasi tipo standard non più grande di n , e se Tnon è un tipo di carattere, new T[n]è necessario solo restituire un indirizzo allineato per T.

Ma questo è rilevante solo quando stai giocando a trucchi specifici dell'implementazione come usare i pochi bit inferiori di un puntatore per memorizzare i flag, o altrimenti fare affidamento sull'indirizzo per avere più allineamento di quanto strettamente necessario.

Non influisce sul riempimento all'interno dell'oggetto, che ha necessariamente esattamente lo stesso layout indipendentemente da come hai allocato la memoria che occupa. Quindi è difficile vedere come la differenza possa causare errori nel trasferimento dei dati.

C'è qualche segno di ciò che l'autore di quel commento pensa degli oggetti in pila o in globali, se a suo parere sono "imbottiti come malloc" o "imbottiti come nuovi"? Questo potrebbe fornire indizi sulla provenienza dell'idea.

Forse è confuso, ma forse il codice di cui sta parlando è molto più di una differenza retta tra malloc(sizeof(Foo) * n)vs new Foo[n]. Forse è più simile a:

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

vs.

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

Cioè, forse sta dicendo "Io uso malloc", ma significa "impacco manualmente i dati in posizioni non allineate invece di usare una struttura". In realtà mallocnon è necessario per impacchettare manualmente la struttura, ma non riuscire a rendersi conto che è un grado minore di confusione. È necessario definire il layout dei dati inviati in rete. Diverse implementazioni riempiranno i dati in modo diverso quando viene utilizzata la struttura .


Grazie per i punti sull'allineamento. I dati in questione sono un array di caratteri, quindi sospetto che non sia una cosa di allineamento qui, né una cosa di struttura, anche se questo è stato anche il mio primo pensiero.
hcarver

5
@ HBcdev: beh, gli chararray non sono mai riempiti, quindi continuerò con "confuso" come spiegazione.
Steve Jessop

5

Il tuo collega potrebbe avere new[]/delete[]in mente il cookie magico di (queste sono le informazioni che l'implementazione usa quando cancella un array). Tuttavia, questo non sarebbe stato un problema se fosse stata utilizzata l'allocazione che inizia all'indirizzo restituito da new[](al contrario di quella dell'allocatore).

L'imballaggio sembra più probabile. Le variazioni negli ABI potrebbero (ad esempio) comportare l'aggiunta di un numero diverso di byte finali alla fine di una struttura (questo è influenzato dall'allineamento, considerate anche gli array). Con malloc, la posizione di una struttura potrebbe essere specificata e quindi più facilmente trasferibile a un ABI estero. Queste variazioni vengono normalmente prevenute specificando l'allineamento e il riempimento delle strutture di trasferimento.


2
Questo era quello che ho pensato per la prima volta, il problema "la struttura è maggiore della somma delle sue parti". Forse è da qui che è nata la sua idea.
hcarver

3

Il layout di un oggetto non può dipendere dal fatto che sia stato allocato utilizzando malloco new. Entrambi restituiscono lo stesso tipo di puntatore e quando si passa questo puntatore ad altre funzioni non sapranno come è stato allocato l'oggetto. sizeof *ptrdipende solo dalla dichiarazione di ptr, non da come è stato assegnato.


3

Penso che tu abbia ragione. Il riempimento è fatto dal compilatore non newo malloc. Le considerazioni sul riempimento si applicherebbero anche se dichiarassi un array o una struttura senza usare newo mallocaffatto. In ogni caso, mentre posso vedere come diverse implementazioni newe mallocpotrebbero causare problemi durante il porting del codice tra piattaforme, non riesco completamente a vedere come potrebbero causare problemi nel trasferimento di dati tra piattaforme.


In precedenza avevo pensato che si potesse considerare newun bel wrapper per, mallocma da altre risposte sembra che non sia del tutto vero. Il consenso sembra essere che il riempimento dovrebbe essere lo stesso con entrambi; Penso che il problema con il trasferimento di dati tra piattaforme si
verifichi

0

Quando voglio controllare il layout della mia semplice vecchia struttura dati, con i compilatori MS Visual che uso #pragma pack(1). Suppongo che una tale direttiva precompilatore sia supportata per la maggior parte dei compilatori, come ad esempio gcc .

Ciò ha la conseguenza di allineare tutti i campi delle strutture uno dietro l'altro, senza spazi vuoti.

Se la piattaforma dall'altra parte fa lo stesso (cioè ha compilato la sua struttura di scambio dati con un riempimento di 1), i dati recuperati su entrambi i lati si adattano perfettamente. Quindi non ho mai dovuto giocare con malloc in C ++.

Nel peggiore dei casi, avrei considerato di sovraccaricare il nuovo operatore in modo che esegua alcune cose complicate, piuttosto che usare malloc direttamente in C ++.


Quali sono le situazioni in cui desideri controllare il layout della struttura dati? Solo curioso.
hcarver

E qualcuno sa di compilatori che supportano pragma packo simili? Mi rendo conto che non farà parte dello standard.
hcarver

gcc lo supporta per esempio. in quale situazione ne avevo bisogno: condivisione di dati binari tra due diverse piattaforme: condivisione di stream binario tra windows e palmOS, tra windows e linux. link su gcc: gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
Stephane Rolland

0

Questa è la mia folle ipotesi sulla provenienza di questa cosa. Come hai detto, il problema è con la trasmissione dei dati tramite MPI.

Personalmente, per le mie complesse strutture di dati che voglio inviare / ricevere su MPI, implemento sempre metodi di serializzazione / deserializzazione che comprimono / decomprimono il tutto in / da un array di caratteri. Ora, a causa del riempimento sappiamo che quella dimensione della struttura potrebbe essere maggiore della dimensione dei suoi membri e quindi è necessario calcolare anche la dimensione non riempita della struttura dati in modo da sapere quanti byte vengono inviati / ricevuti.

Ad esempio, se si desidera inviare / ricevere std::vector<Foo> Atramite MPI con la suddetta tecnica, è sbagliato presumere che la dimensione della matrice di caratteri risultante sia A.size()*sizeof(Foo)in generale. In altre parole, ogni classe che implementa metodi serialize / deserialize, dovrebbe implementare anche un metodo che riporta la dimensione dell'array (o meglio ancora memorizzare l'array in un contenitore). Questo potrebbe diventare il motivo alla base di un bug. In un modo o nell'altro, tuttavia, ciò non ha nulla a che fare con newvs malloccome sottolineato in questo thread.


La copia su array di caratteri può essere problematica: è possibile che alcuni dei core siano su architetture little-endian e alcuni big-endian (forse non probabile, ma possibile). Dovresti codificarli XDR o qualcosa del genere, ma potresti semplicemente usare tipi di dati MPI definiti dall'utente. Tengono facilmente conto dell'imbottitura. Ma posso vedere cosa stai dicendo sulla possibile causa di incomprensione - è quello che chiamo il problema "la struttura è maggiore della somma delle sue parti".
hcarver

Sì, la definizione dei tipi di dati MPI è un altro / modo corretto per farlo. Buon punto sull'endianness. Tuttavia, dubito davvero che ciò accada su cluster reali. Ad ogni modo, ho pensato che se seguissero la stessa strategia, ciò potrebbe portare a bug ...
mmirzadeh

0

In c ++: la new parola chiave è usata per allocare alcuni particolari byte di memoria rispetto ad alcune strutture dati. Ad esempio, hai definito una classe o una struttura e vuoi allocare memoria per il suo oggetto.

myclass *my = new myclass();

o

int *i = new int(2);

Ma in tutti i casi è necessario il tipo di dati definito (class, struct, union, int, char ecc ...) e verranno allocati solo quei byte di memoria necessari per il suo oggetto / variabile. (cioè; multipli di quel tipo di dati).

Ma nel caso del metodo malloc (), puoi allocare qualsiasi byte di memoria e non è necessario specificare il tipo di dati in ogni momento. Qui puoi osservarlo in alcune possibilità di malloc ():

void *v = malloc(23);

o

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

o

char *c = (char*)malloc(sizeof(char)*35);

-1

malloc è un tipo di funzione e new è un tipo di tipo di dati in c ++ in c ++, se usiamo malloc di quanto dobbiamo e dovremmo usare typecast altrimenti il ​​compilatore ti darà un errore e se usiamo un nuovo tipo di dati per l'allocazione della memoria di quanto non ne abbiamo bisogno per typecast


1
Penso che dovresti provare a discutere un po 'di più la tua risposta.
Carlo

Questo non sembra affrontare la questione di loro che fanno cose diverse con le imbottiture, che è quello che stavo chiedendo in realtà sopra.
hcarver
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.