Malloc vs New - inna wyściółka

110

Sprawdzam czyjś kod C ++ dla naszego projektu, który używa MPI do obliczeń o wysokiej wydajności (10 ^ 5 - 10 ^ 6 rdzeni). Kod ma umożliwić komunikację między (potencjalnie) różnymi maszynami na różnych architekturach. Napisał komentarz, który mówi coś w rodzaju:

Zwykle używamy newi delete, ale tutaj używam malloci free. Jest to konieczne, ponieważ niektóre kompilatory będą wypełniać dane inaczej, gdy newsą używane, co prowadzi do błędów w przesyłaniu danych między różnymi platformami. To się nie dzieje z malloc.

To nie pasuje do niczego, co znam z pytań standardowych newi mallocpytań.

Jaka jest różnica między new / delete i malloc / free? podpowiada, że ​​kompilator może inaczej obliczyć rozmiar obiektu (ale dlaczego różni się to od używania sizeof?).

malloc i umieszczenie nowe kontra nowe to dość popularne pytanie, ale mówi tylko o newużywaniu konstruktorów tam, gdzie mallocnie, co nie ma tu znaczenia.

jak Malloc rozumie wyrównanie? mówi, że pamięć jest na pewno odpowiednio wyrównana z jednym newlub z malloctym, co wcześniej myślałem.

Domyślam się, że jakiś czas w przeszłości błędnie zdiagnozował swój własny błąd i wydedukował to newimalloc podał różne ilości wypełnienia, co moim zdaniem prawdopodobnie nie jest prawdą. Ale nie mogę znaleźć odpowiedzi w Google ani w żadnym poprzednim pytaniu.

Pomóż mi, StackOverflow, jesteś moją jedyną nadzieją!

hcarver
źródło
33
+1 za samo badanie różnych wątków SO!
iammilind
7
+1 Z łatwością jedno z najlepszych zadań badawczych typu „pomóż sobie, zanim poproszę innych”, jakie widziałem na SO w DŁUGIM czasie. Chciałbym móc zagłosować jeszcze kilka razy.
WhozCraig
1
Czy kod transferu zakłada, że ​​dane są wyrównane w jakikolwiek określony sposób, np. Że zaczynają się na granicy ośmiu bajtów? Może się to różnić w zależności od malloci new, ponieważ neww niektórych środowiskach przydziela się blok, dodaje pewne dane na początku i zwraca wskaźnik do lokalizacji tuż po tych danych. (Zgadzam się z innymi, wewnątrz bloku danych malloci newmuszę użyć tego samego rodzaju wypełnienia.)
Lindydancer
1
Wow , nie spodziewałem się, że to pytanie będzie tak popularne! @Lindydancer, nie sądzę, aby zakładano jakąkolwiek granicę 8-bajtową. Ciekawa uwaga.
hcarver
1
Jednym z powodów używania jednej metody alokacji zamiast innej jest sytuacja, w której „ktoś inny” wypuszcza obiekt. Jeśli ten „ktoś inny” usunie obiekt używając free, musisz przydzielić go za pomocą malloc. (Problemem jest czerwony śledź.)
Lindydancer

Odpowiedzi:

25

IIRC jest jeden wybredny punkt. mallocgwarantuje zwrócenie adresu wyrównanego dla dowolnego typu standardowego. ::operator new(n)gwarantuje tylko zwrócenie adresu wyrównanego dla dowolnego typu standardowego nie większego niż n , a jeśli Tnie jest typem znakowym, new T[n]wymagane jest tylko zwrócenie adresu wyrównanego dlaT .

Jest to jednak istotne tylko wtedy, gdy wykonujesz sztuczki specyficzne dla implementacji, takie jak używanie kilku dolnych bitów wskaźnika do przechowywania flag lub w inny sposób poleganie na adresie, aby uzyskać więcej wyrównania niż jest to ściśle potrzebne.

Nie wpływa na wypełnienie w obiekcie, który z konieczności ma dokładnie ten sam układ, niezależnie od tego, jak przydzielono zajmowaną przez niego pamięć. Trudno więc zrozumieć, w jaki sposób różnica może spowodować błędy w przesyłaniu danych.

Czy jest jakiś znak, co autor tego komentarza myśli o obiektach na stosie lub w globach, czy jego zdaniem są one „wyściełane jak malloc”, czy „wyściełane jak nowe”? To może dać wskazówki, skąd wziął się pomysł.

Może on mylić, ale może kod on mówi jest czymś więcej niż prostą różnicę między malloc(sizeof(Foo) * n)vs new Foo[n]. Może bardziej przypomina:

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

vs.

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

To znaczy, może mówi „używam malloc”, ale ma na myśli „ręcznie pakuję dane w niewyrównane lokalizacje zamiast używać struktury”. W rzeczywistości mallocnie jest potrzebne do ręcznego pakowania struktury, ale nie zdawanie sobie sprawy, że jest to mniejszy stopień zamieszania. Konieczne jest zdefiniowanie układu danych przesyłanych przewodem. Różne implementacje będą wypełniać dane inaczej, gdy używana jest struktura .

Steve Jessop
źródło
Dzięki za uwagi dotyczące wyrównania. Dane, o których mowa, to tablica znaków, więc podejrzewam, że nie jest to kwestia wyrównania ani struktura - chociaż to też była moja pierwsza myśl.
hcarver
5
@Hbcdev: cóż, chartablice nigdy nie są w ogóle uzupełniane , więc będę trzymał się wyjaśnienia „zdezorientowany”.
Steve Jessop
5

Twój kolega mógł mieć new[]/delete[]na myśli magiczne ciasteczko (są to informacje używane przez implementację podczas usuwania tablicy). Nie stanowiłoby to jednak problemu, gdyby zastosowano alokację rozpoczynającą się od adresu zwróconego przez new[](w przeciwieństwie do alokatora).

Bardziej prawdopodobne wydaje się zapakowanie . Różnice w ABI mogą (na przykład) skutkować różną liczbą końcowych bajtów dodanych na końcu struktury (ma na to wpływ wyrównanie, należy również wziąć pod uwagę tablice). Dzięki malloc położenie konstrukcji można określić, a tym samym łatwiej przenieść do obcego ABI. Zmiennościom tym zwykle zapobiega się przez określenie wyrównania i upakowania struktur transferowych.

Justin
źródło
2
To właśnie pomyślałem na początku, problem „struktura jest większa niż suma jej części”. Być może stąd pochodzi jego pomysł.
hcarver
3

Układ obiektu nie może zależeć od tego, czy został przydzielony za pomocą, mallocczy new. Oba zwracają ten sam rodzaj wskaźnika, a gdy przekażesz ten wskaźnik do innych funkcji, nie będą wiedzieć, w jaki sposób obiekt został przydzielony. sizeof *ptrzależy tylko od deklaracji ptr, a nie od tego , jak została przypisana.

Barmar
źródło
3

Myślę, że masz rację. Wypełnienie jest wykonywane przez kompilator nie newlub malloc. Zagadnienia dotyczące wypełnienia będą miały zastosowanie nawet wtedy, gdy zadeklarujesz tablicę lub strukturę bez użycia newlub mallocw ogóle. W każdym razie, chociaż widzę, jak różne implementacje newi mallocmogą powodować problemy podczas przenoszenia kodu między platformami, całkowicie nie widzę, jak mogą powodować problemy z przesyłaniem danych między platformami.

Jan
źródło
Wcześniej zakładałem, że możesz uznać newza fajne opakowanie, mallocale z innych odpowiedzi wydaje się, że nie jest to do końca prawdą. Wydaje się, że konsensus jest taki, że wypełnienie powinno być takie samo w przypadku obu; Myślę, że problem z przesyłaniem danych między platformami pojawia się tylko wtedy, gdy twój mechanizm transferu jest uszkodzony :)
hcarver
0

Kiedy chcę kontrolować układ mojej zwykłej starej struktury danych, używam kompilatorów MS Visual #pragma pack(1). Przypuszczam, że taka dyrektywa prekompilatora jest obsługiwana przez większość kompilatorów, takich jak na przykład gcc .

Skutkuje to wyrównaniem wszystkich pól struktur jedno za drugim, bez pustych przestrzeni.

Jeśli platforma na drugim końcu robi to samo (tj. Skompilowała swoją strukturę wymiany danych z wypełnieniem 1), to dane odzyskane po obu stronach pasują po prostu dobrze. Dlatego nigdy nie musiałem bawić się mallocem w C ++.

W najgorszym przypadku rozważałbym przeciążenie nowego operatora, ponieważ wykonuje on kilka trudnych rzeczy, zamiast używać malloc bezpośrednio w C ++.

Stephane Rolland
źródło
W jakich sytuacjach chcesz kontrolować układ struktury danych? Po prostu ciekawy.
hcarver
Czy ktoś wie o kompilatorach obsługujących pragma packlub podobnych? Zdaję sobie sprawę, że nie będzie to częścią standardu.
hcarver
gcc obsługuje to na przykład. w jakiej sytuacji tego potrzebowałem: współdzielenie danych binarnych między dwoma różnymi platformami: udostępnianie strumienia binarnego między oknami i palmOS, między oknami i linuxem. linki o gcc: gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
Stephane Rolland
0

To jest moje dzikie przypuszczenie, skąd pochodzi ta rzecz. Jak wspomniałeś, problem dotyczy transmisji danych przez MPI.

Osobiście, dla moich skomplikowanych struktur danych, które chcę wysyłać / odbierać przez MPI, zawsze implementuję metody serializacji / deserializacji, które pakują / rozpakowują całość do / z tablicy znaków. Teraz, dzięki wypełnieniu, wiemy, że ten rozmiar struktury może być większy niż rozmiar jej elementów, a zatem należy również obliczyć niezabudowany rozmiar struktury danych, abyśmy wiedzieli, ile bajtów jest wysyłanych / odbieranych.

Na przykład, jeśli chcesz wysyłać / odbierać std::vector<Foo> Aprzez MPI tą techniką, błędem jest zakładanie, że rozmiar wynikowej tablicy znaków jest A.size()*sizeof(Foo)ogólnie. Innymi słowy, każda klasa, która implementuje metody serializacji / deserializacji, powinna również implementować metodę, która zgłasza rozmiar tablicy (lub jeszcze lepiej przechowywać tablicę w kontenerze). To może stać się przyczyną błędu. Tak czy inaczej, nie ma to jednak nic wspólnego z newvs, mallocjak wskazano w tym wątku.

mmirzadeh
źródło
Kopiowanie do tablic char może być problematyczne - możliwe, że niektóre z twoich rdzeni są na architekturach little-endian, a inne na big-endianach (może mało prawdopodobne, ale możliwe). Musiałbyś je zakodować w XDR lub coś w tym rodzaju, ale możesz po prostu użyć zdefiniowanych przez użytkownika typów danych MPI. Z łatwością uwzględniają wyściółkę. Ale widzę, co mówisz o możliwej przyczynie nieporozumienia - to właśnie nazywam problemem „struktura jest większa niż suma jej części”.
hcarver
Tak, zdefiniowanie typów danych MPI jest kolejnym / poprawnym sposobem na zrobienie tego. Dobra uwaga na temat endianizmu. Chociaż naprawdę wątpię, że stanie się to na rzeczywistych klastrach. W każdym razie pomyślałem, że jeśli zastosują tę samą strategię, może to doprowadzić do błędów ...
mmirzadeh
0

W języku c ++: new słowo kluczowe służy do przydzielania określonych bajtów pamięci w odniesieniu do jakiejś struktury danych. Na przykład zdefiniowałeś jakąś klasę lub strukturę i chcesz przydzielić pamięć dla jej obiektu.

myclass *my = new myclass();

lub

int *i = new int(2);

Ale we wszystkich przypadkach potrzebujesz zdefiniowanego typu danych (class, struct, union, int, char itp.) I przydzielone zostaną tylko te bajty pamięci, które są wymagane dla obiektu / zmiennej. (tj. wielokrotności tego typu danych).

Ale w przypadku metody malloc () możesz przydzielić dowolne bajty pamięci i nie musisz cały czas określać typu danych. Tutaj możesz to zaobserwować w kilku możliwościach malloc ():

void *v = malloc(23);

lub

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

lub

char *c = (char*)malloc(sizeof(char)*35);
Rahul Raina
źródło
-1

malloc jest typem funkcji, a new jest typem danych w c ++ w c ​​++, jeśli używamy malloc niż musimy i powinniśmy używać typecast, w przeciwnym razie kompilator daje błąd i jeśli używamy nowego typu danych do alokacji pamięci, niż nie potrzebujemy na maszynie

hk_043
źródło
1
Myślę, że powinieneś spróbować nieco bardziej argumentować swoją odpowiedź.
Carlo
To nie wydaje się odpowiadać na pytanie, czy robią różne rzeczy z wyściółkami, o co naprawdę pytałem powyżej.
hcarver