(Tło: Mam doświadczenie w implementacji kompilatorów C i C ++.)
Tablice o zmiennej długości w C99 były w zasadzie błędem. Aby wesprzeć VLA, C99 musiał poczynić następujące ustępstwa wobec zdrowego rozsądku:
sizeof x
nie jest już zawsze stałą czasową kompilacji; kompilator musi czasem wygenerować kod, aby ocenić sizeof
-wyrażenie w czasie wykonywania.
Pozwalając Włas dwuwymiarowe ( int A[x][y]
) wymagane nową składnię deklarowania funkcji, które mają 2D Włas jako parametry: void foo(int n, int A[][*])
.
Mniej ważne w świecie C ++, ale niezwykle ważne dla docelowej grupy C programistów systemów wbudowanych, zadeklarowanie VLA oznacza przełamanie dowolnej dużej części stosu. Jest to gwarantowane przepełnienie stosu i awaria. (Zawsze możesz zadeklarować int A[n]
, jesteś pośrednio twierdząc, że masz 2GB stosu oszczędzić. Po tym wszystkim, jeśli znasz „ n
jest zdecydowanie mniej niż 1000 Here”, wtedy po prostu zadeklarować int A[1000]
. Podstawiając 32-bitową liczbę całkowitą n
za 1000
to wstęp że nie masz pojęcia, jakie powinno być zachowanie twojego programu).
OK, przejdźmy teraz do rozmowy o C ++. W C ++ mamy takie samo rozróżnienie pomiędzy „systemem typów” i „systemem wartości”, co C89… ale tak naprawdę zaczęliśmy na nim polegać w sposób, w jaki C nie. Na przykład:
template<typename T> struct S { ... };
int A[n];
S<decltype(A)> s; // equivalently, S<int[n]> s;
Gdyby n
nie była stała czasowa kompilacji (tj. Gdyby A
była zmiennie zmodyfikowanego typu), to czym, u licha, byłby typ S
? Czy S
typ byłby również określany tylko w czasie wykonywania?
A co z tym:
template<typename T> bool myfunc(T& t1, T& t2) { ... };
int A1[n1], A2[n2];
myfunc(A1, A2);
Kompilator musi wygenerować kod dla określonej instancji myfunc
. Jak powinien wyglądać ten kod? Jak możemy statycznie wygenerować ten kod, jeśli nie znamy typu A1
w czasie kompilacji?
Co gorsza, co się stanie, jeśli okaże się to w czasie wykonywania n1 != n2
, tak !std::is_same<decltype(A1), decltype(A2)>()
? W takim przypadku wywołanie do myfunc
nie powinno się nawet kompilować , ponieważ dedukcja typu szablonu powinna zakończyć się niepowodzeniem! Jak możemy naśladować to zachowanie w czasie wykonywania?
Zasadniczo C ++ zmierza w kierunku popychania coraz większej liczby decyzji do czasu kompilacji : generowania kodu szablonu, constexpr
oceny funkcji i tak dalej. Tymczasem C99 był zajęty popychanie tradycyjnie kompilacji decyzje (np sizeof
) do wykonywania . Mając to na uwadze, czy naprawdę warto w ogóle podejmować wysiłki, próbując zintegrować VLA w stylu C99 z C ++?
Jak już zauważył każdy inny odpowiadający, C ++ zapewnia wiele mechanizmów alokacji sterty ( std::unique_ptr<int[]> A = new int[n];
lub std::vector<int> A(n);
oczywiste), gdy naprawdę chcesz przekazać ideę „Nie mam pojęcia, ile pamięci RAM może potrzebować”. A C ++ zapewnia sprytny model obsługi wyjątków do radzenia sobie z nieuchronną sytuacją, w której potrzebna ilość pamięci RAM jest większa niż ilość pamięci RAM, którą masz. Ale mam nadzieję, że ta odpowiedź daje dobre wyobrażenie o tym, dlaczego VLA w stylu C99 nie pasowały do C ++ - a nawet nie pasowały do C99. ;)
Aby uzyskać więcej informacji na ten temat, patrz N3810 „Alternatywy dla rozszerzeń macierzy” , artykuł Bjarne Stroustrup z października 2013 r. Na temat VLA. POV Bjarne'a bardzo różni się od mojego; N3810 skupia się bardziej na znalezieniu dobrej składni języka C ++ dla rzeczy i na zniechęcaniu do używania surowych tablic w C ++, podczas gdy bardziej skupiłem się na implikacjach dla metaprogramowania i systemu typów. Nie wiem, czy uważa, że implikacje metaprogramowania / systemu typów są rozwiązane, możliwe do rozwiązania lub po prostu nieciekawe.
Dobry post na blogu, który dotyka wielu z tych samych punktów, to „Uzasadnione użycie tablic o zmiennej długości” (Chris Wellons, 27.10.2019).