(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 xnie 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 „ njest zdecydowanie mniej niż 1000 Here”, wtedy po prostu zadeklarować int A[1000]. Podstawiając 32-bitową liczbę całkowitą nza 1000to 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 nnie była stała czasowa kompilacji (tj. Gdyby Abyła zmiennie zmodyfikowanego typu), to czym, u licha, byłby typ S? Czy Styp 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 A1w 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, constexproceny 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).