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?


źródło
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.]

Faisal Vali
źródło
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ąć.


źródło
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.

ChrisW
źródło
„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.

Evan Teran
źródło
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”.

shuaihanhungry
źródło
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].

Shi Jieming
źródło