C ++ new int [0] - czy przydzieli pamięć?


241

Prosta aplikacja testowa:

cout << new int[0] << endl;

wyjścia:

0x876c0b8

Wygląda na to, że działa. Co standard mówi o tym? Czy zawsze „przydzielanie” pustego bloku pamięci jest legalne?


4
+1 Bardzo interesujące pytanie - chociaż nie jestem pewien, jak bardzo to ma znaczenie w prawdziwym kodzie.
— Zifre,

37
@Zifre: Proszę o ciekawość, ale może to mieć znaczenie w świecie rzeczywistym, np. Gdy wielkość przydzielonych bloków pamięci jest w jakiś sposób obliczana, a wynik obliczeń może wynosić zero, wtedy nie ma potrzeby bezpośredniego dodawania wyjątków aby nie przydzielać bloków o zerowym rozmiarze. Ponieważ powinny być one przydzielane i usuwane bez błędów (jeśli tylko blok o zerowym rozmiarze nie jest wyłączony). Ogólnie daje to szerszą abstrakcję tego, czym jest blok pamięci.

2
@ emg-2: W twojej przykładowej sytuacji tak naprawdę nie miałoby to znaczenia, ponieważ delete [] jest całkowicie legalne dla wskaźnika NULL :-).
— Evan Teran,

2
Jest to tylko stycznie powiązane - więc tutaj komentuję - ale C ++ na wiele sposobów zapewnia, że ​​różne obiekty mają unikalne adresy ... nawet jeśli nie wymagają jawnego przechowywania. Powiązanym eksperymentem byłoby sprawdzenie wielkości pustej struktury. Lub tablica tej struktury.
— Drew Dormann,

2
W celu rozwinięcia komentarza Shmoopty: Zwłaszcza podczas programowania za pomocą szablonów (np. Szablonów klas strategii, takich jak std :: alokator), w C ++ powszechne jest posiadanie obiektów o zerowej wielkości. Kod ogólny może wymagać dynamicznego przydzielania takich obiektów i używania do nich wskaźników do porównywania tożsamości obiektów. Dlatego operator new () zwraca unikalne wskaźniki dla żądań o zerowej wielkości. Chociaż prawdopodobnie mniej ważne / wspólne, to samo rozumowanie dotyczy alokacji tablic i operatora new [] ().
— Trevor Robinson

Odpowiedzi:


233

Od 5.3.4 / 7

Gdy wartość wyrażenia w bezpośrednim nowym deklaratorze wynosi zero, funkcja alokacji jest wywoływana w celu alokacji tablicy bez elementów.

Od 3.7.3.1/2

Efekt dereferencji wskaźnika zwróconego jako żądanie zerowego rozmiaru jest niezdefiniowany.

Również

Nawet jeśli wielkość żądanego miejsca [przez new] wynosi zero, żądanie może się nie powieść.

Oznacza to, że możesz to zrobić, ale nie możesz legalnie (w dobrze zdefiniowany sposób na wszystkich platformach) wyłuskać dostępnej pamięci - możesz tylko przekazać ją do skasowania tablicy - i powinieneś ją usunąć.

Oto interesująca notatka (tj. Nie normatywna część normy, ale dołączona do celów ekspozycyjnych) dołączona do zdania z 3.7.3.1/2

[32. Chodzi o to, aby operator new () mógł zostać wdrożony przez wywołanie malloc () lub calloc (), więc reguły są zasadniczo takie same. C ++ różni się od C wymaganiem zerowego żądania do zwrócenia wskaźnika niepustego.]


Dostaję wyciek pamięci, jeśli nie usunę. Czy jest to oczekiwane? Przynajmniej się nie spodziewałem.
— EralpB,

12
@EralpB: wręcz przeciwnie, nawet jeśli twoje żądanie wynosi zero, alokacja ta odbywa się na stosie, gdzie jedno żądanie wiąże się z kilkoma operacjami prowadzenia ksiąg rachunkowych, takimi jak alokacja i inicjowanie ochrony stosów przed i po strefie podanej przez alokatora, wstawienie do wolnych list inne złożone, okropne struktury. Uwolnienie go oznacza wykonanie księgowości wstecznej.
— v.oddou

3
@EralpB tak, myślę, że możesz spodziewać się wycieku pamięci za każdym razem, gdy nie balansujesz new[]z delete[]- niezależnie od wielkości. W szczególności, gdy dzwonisz new[i], potrzebujesz nieco więcej pamięci niż ta, którą próbujesz przydzielić, aby przechowywać rozmiar tablicy (która jest później używana delete[]podczas
— dezalokacji

24

Tak, legalne jest przydzielanie tablicy o zerowej wielkości w ten sposób. Ale musisz go również usunąć.


1
Czy masz na to powód? Wszyscy wiemy, że int ar[0];jest to nielegalne, dlaczego nowy jest OK?
— Motti

4
Interesujące jest to, że C ++ nie jest tak silny w zabranianiu zerowych obiektów. Pomyśl o optymalizacji pustej klasy podstawowej: tutaj również pusty pod-obiekt klasy podstawowej może mieć rozmiar zero. W przeciwieństwie do tego, standard C dokłada wszelkich starań, aby nigdy nie powstały obiekty o zerowej wielkości: definiując malloc (0), mówi on na przykład, że skutkiem jest uruchomienie malloc z jakimś niezerowym argumentem, na przykład. I w struct {...; T n []; }; gdy nie ma przydzielonej przestrzeni dla tablicy (FAM), oznacza to, że zachowuje się tak, jakby „n” miał jeden element. (W obu przypadkach używanie obiektu w jakikolwiek sposób to UB, np. Xn [0])
— Johannes Schaub - litb

1
Myślę, że sizeof (type)oczekuje się, że nigdy nie zwróci zera. Patrz na przykład: stackoverflow.com/questions/2632021/can-sizeof-return-0-zero
— pqnet

Nawet tak zwana „optymalizacja pustej klasy podstawowej” jest istotna tylko ze względu na to, że wszystkie obiekty (w przeciwieństwie do wszystkich bajtów w obiektach) muszą mieć unikalne adresy. C ++ można by uprościć, gdyby kod, który faktycznie troszczył się o obiekty posiadające unikalne adresy, był wymagany, aby zapewnić, że mają niezerowy rozmiar.
— supercat

15

Co standard mówi o tym? Czy zawsze „przydzielanie” pustego bloku pamięci jest legalne?

Każdy obiekt ma unikalną tożsamość, tj. Unikalny adres, co oznacza niezerową długość (rzeczywista ilość pamięci zostanie cicho zwiększona, jeśli poprosisz o zero bajtów).

Jeśli przydzielono więcej niż jeden z tych obiektów, okazałoby się, że mają one różne adresy.


„Każdy obiekt ma unikalną tożsamość, tj. Unikalny adres, co oznacza niezerową długość” - prawda, ale źle. Obiekt ma adres, ale wskaźnik do obiektu może wskazywać losową pamięć. „Rzeczywista ilość pamięci zostanie cicho zwiększona, jeśli poprosisz o zero bajtów” - nie jestem pewien. operator []przechowuje również gdzieś rozmiar tablicy (patrz isocpp.org/wiki/faq/freestore-mgmt#num-elems-in-new-array ). Więc jeśli implementacja przydzieli liczbę bajtów z danymi, może po prostu przydzielić dla liczby bajtów i 0 bajtów dla danych, zwracając wskaźnik 1-ostatni-ostatni.
— Steed,

Niezerowa długość przydzielonej pamięci, a nie niezerowa długość użytecznej pamięci. Chodziło mi o to, że dwa wskaźniki do dwóch różnych obiektów nie powinny wskazywać tego samego adresu.
— ChrisW

1
Standard ( open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf ) rzeczywiście mówi (3.7.4.1/2), że różne wywołania operator new[]powinny zwracać różne wskaźniki. BTW, new expressionma kilka dodatkowych zasad (5.3.4). Nie mogłem znaleźć żadnej wskazówki, która newprzy rozmiarze 0 jest faktycznie wymagana do przydzielenia czegokolwiek. Przepraszam, przegłosowałem, ponieważ uważam, że twoja odpowiedź nie odpowiada na pytania, ale zawiera pewne kontrowersyjne stwierdzenia.
— Steed,

@ Kontynuuj pamięć do przechowywania długości bloku może być niejawnie obliczona przy użyciu przestrzeni adresowej. Na przykład dla tablic o zerowej wielkości może być możliwe, aby new[]implementacja zwracała adresy w zakresie, w którym nie ma dynamicznej pamięci mapowanej, a więc tak naprawdę nie używa żadnej pamięci (podczas korzystania z przestrzeni adresowej)
— pqnet

@pqnet: Dobrej jakości wdrożenie powinno pozwolić na nieograniczoną liczbę cykli nowych / usuwania, czyż nie? Na platformie z 64-bitowymi wskaźnikami rozsądne może być stwierdzenie, że komputer wykonujący while(!exitRequested) { char *p = new char[0]; delete [] p; }pętlę bez wskaźników ponownego przetwarzania zapadłby się w pył, zanim mógł zabraknąć przestrzeni adresowej, ale na platformie z 32-bitowymi wskaźnikami byłby to znacznie mniej uzasadnione założenie.
— supercat

14

Tak, jest całkowicie legalne, aby przydzielić 0rozmiar bloku new. Po prostu nie możesz zrobić nic przydatnego, ponieważ nie ma ważnych danych, do których miałbyś dostęp. int[0] = 5;jest nielegalne.

Uważam jednak, że standard pozwala na rzeczy takie jak malloc(0)zwrot NULL.

Nadal będziesz potrzebował delete []wskaźnika, który otrzymasz z przydziału.


5
Jeśli chodzi o malloc, masz rację - jest to implementacja zdefiniowana. Jest to często postrzegane jako błąd.

Przypuszczam, że interesujące pytanie brzmi: czy nowa wersja nothrow może zwrócić NULL, jeśli otrzyma rozmiar 0?
— Evan Teran

1
Semantyka new i malloc nie są w żaden sposób powiązane, przynajmniej przez standard.

nie mówiąc, że są ... pytał konkretnie o nową wersję nothrow.
— Evan Teran

@Evan - nowa wersja nothrow zwraca wartość null tylko wtedy, gdy żądanie się nie powiedzie - nie tylko, jeśli wielkość żądania wynosi 0 @ Neil - normalnie niepowiązane - ale powiązane celowo (tj. Operator nowy może zostać zaimplementowany w kategoriach malloc, ale nie inny na odwrót) - patrz przypis, który zawarłem w mojej odpowiedzi
— Faisal Vali,

1

Co ciekawe, C ++ wymaga, aby nowy operator zwrócił prawidłowy wskaźnik, nawet gdy wymagane jest zero bajtów. (Wymaganie tego dziwnie brzmiącego zachowania upraszcza sprawy w innym języku).

Znalazłem Effective C ++ Third Edition w następujący sposób: „Punkt 51: Przestrzegaj konwencji podczas pisania nowego i usuwania”.


0

Gwarantuję ci, że nowy int [0] kosztuje dodatkowe miejsce, odkąd go przetestowałem.

Na przykład użycie pamięci przez

int **arr = new int*[1000000000];

jest znacznie mniejszy niż

int **arr = new int*[1000000000];
for(int i =0; i < 1000000000; i++) {
    arr[i]=new int[0];
}

Wykorzystanie pamięci drugiego fragmentu kodu minus zużycie pierwszego fragmentu kodu to pamięć używana przez liczne nowe int [0].

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.