Ich habe C in den letzten Jahren nicht sehr oft benutzt. Als ich diese Frage heute las , stieß ich auf eine C-Syntax, mit der ich nicht vertraut war.
Anscheinend ist in C99 die folgende Syntax gültig:
void foo(int n) {
int values[n]; //Declare a variable length array
}
Dies scheint eine ziemlich nützliche Funktion zu sein. Gab es jemals eine Diskussion darüber, es dem C ++ - Standard hinzuzufügen, und wenn ja, warum wurde es weggelassen?
Einige mögliche Gründe:
- Haarig für Compiler-Anbieter zu implementieren
- Inkompatibel mit einem anderen Teil des Standards
- Die Funktionalität kann mit anderen C ++ - Konstrukten emuliert werden
Der C ++ - Standard besagt, dass die Arraygröße ein konstanter Ausdruck sein muss (8.3.4.1).
Ja, natürlich ist mir klar, dass man im Spielzeugbeispiel verwenden könnte std::vector<int> values(m);
, aber dies ordnet Speicher vom Heap und nicht vom Stapel zu. Und wenn ich ein mehrdimensionales Array möchte wie:
void foo(int x, int y, int z) {
int values[x][y][z]; // Declare a variable length array
}
Die vector
Version wird ziemlich ungeschickt:
void foo(int x, int y, int z) {
vector< vector< vector<int> > > values( /* Really painful expression here. */);
}
Die Slices, Zeilen und Spalten werden möglicherweise auch über den gesamten Speicher verteilt.
Wenn man sich die Diskussion anschaut comp.std.c++
, ist klar, dass diese Frage mit einigen sehr schwergewichtigen Namen auf beiden Seiten des Arguments ziemlich kontrovers ist. Es ist sicherlich nicht offensichtlich, dass a std::vector
immer eine bessere Lösung ist.
quelle
Antworten:
Vor kurzem gab es eine Diskussion darüber, die im Usenet gestartet wurde: Warum keine VLAs in C ++ 0x .
Ich stimme den Leuten zu, die der Meinung zu sein scheinen, dass es nicht gut ist, ein potenziell großes Array auf dem Stapel zu erstellen, für das normalerweise nur wenig Speicherplatz verfügbar ist. Das Argument ist, wenn Sie die Größe vorher kennen, können Sie ein statisches Array verwenden. Und wenn Sie die Größe vorher nicht kennen, schreiben Sie unsicheren Code.
C99-VLAs könnten einen kleinen Vorteil darin haben, kleine Arrays erstellen zu können, ohne Speicherplatz zu verschwenden oder Konstruktoren für nicht verwendete Elemente aufzurufen, aber sie führen zu ziemlich großen Änderungen am Typsystem (Sie müssen in der Lage sein, Typen abhängig von den Laufzeitwerten anzugeben - dies existiert in aktuellem C ++ noch nicht, außer für
new
Operator-Typ-Spezifizierer, aber sie werden speziell behandelt, damit die Laufzeit nicht dem Bereich desnew
Operators entgeht.Sie können verwenden
std::vector
, aber es ist nicht ganz dasselbe, da es dynamischen Speicher verwendet, und es ist nicht gerade einfach, den eigenen Stapelzuweiser zu verwenden (auch die Ausrichtung ist ein Problem). Es löst auch nicht das gleiche Problem, da ein Vektor ein Container mit veränderbarer Größe ist, während VLAs eine feste Größe haben. Mit dem Vorschlag für ein dynamisches C ++ - Array soll eine bibliotheksbasierte Lösung als Alternative zu einer sprachbasierten VLA eingeführt werden. Soweit ich weiß, wird es jedoch nicht Teil von C ++ 0x sein.quelle
T(*)[]
zu aT(*)[N]
- in C ++ ist dies nicht zulässig, da C ++ weiß nichts über "Typkompatibilität" - es erfordert genaue Übereinstimmungen), Typparameter, Ausnahmen, Konstruktoren und Destruktoren und so. Ich bin mir nicht sicher, ob sich die Vorteile von VLAs wirklich auszahlen würden. Aber dann habe ich im wirklichen Leben noch nie VLAs verwendet, daher kenne ich wahrscheinlich keine guten Anwendungsfälle für sie.vector
aber ein festes LIFO-Verwendungsmuster erfordert und einen oder mehrere statisch zugewiesene Puffer pro Thread verwaltet, die im Allgemeinen entsprechend der größten Gesamtzuordnung des Threads dimensioniert sind jemals verwendet, aber die könnte explizit gekürzt werden. Eine normale "Zuordnung" würde im allgemeinen Fall nichts weiter als eine Zeigerkopie, eine Zeiger-von-Zeiger-Subtraktion, einen ganzzahligen Vergleich und eine Zeigeraddition erfordern; Die Aufhebung der Zuordnung würde lediglich eine Zeigerkopie erfordern. Nicht viel langsamer als ein VLA.(Hintergrund: Ich habe einige Erfahrung mit der Implementierung von C- und C ++ - Compilern.)
Arrays mit variabler Länge in C99 waren im Grunde ein Fehltritt. Um VLAs zu unterstützen, musste C99 dem gesunden Menschenverstand folgende Zugeständnisse machen:
sizeof x
ist nicht mehr immer eine Kompilierungszeitkonstante; Der Compiler muss manchmal Code generieren, um einensizeof
Ausdruck zur Laufzeit auszuwerten .Das Zulassen von zweidimensionalen VLAs (
int A[x][y]
) erforderte eine neue Syntax zum Deklarieren von Funktionen, die 2D-VLAs als Parameter verwenden :void foo(int n, int A[][*])
.In der C ++ - Welt weniger wichtig, aber für Cs Zielgruppe von Programmierern für eingebettete Systeme äußerst wichtig, bedeutet das Deklarieren einer VLA, einen beliebig großen Teil Ihres Stapels zu zerlegen . Dies ist ein garantierter Stapelüberlauf und Absturz. (Jedes Mal
int A[n]
, wenn Sie deklarieren , behaupten Sie implizit, dass Sie 2 GB Stack übrig haben. Wenn Sie wissen, dass "n
hier definitiv weniger als 1000 sind", deklarieren Sie dies einfachint A[1000]
. Das Ersetzen der 32-Bit-Ganzzahln
durch1000
ist eine Zulassung dass Sie keine Ahnung haben, wie sich Ihr Programm verhalten soll.)Okay, lassen Sie uns jetzt über C ++ sprechen. In C ++ haben wir die gleiche starke Unterscheidung zwischen "Typsystem" und "Wertesystem" wie in C89 ... aber wir haben wirklich begonnen, uns auf eine Weise darauf zu verlassen, die C nicht hat. Zum Beispiel:
Wenn
n
es keine Kompilierungszeitkonstante gäbe (dh wennA
sie vom variabel modifizierten Typ wäre), was um alles in der Welt wäre dann der TypS
? WürdeS
‚s Art auch nur zur Laufzeit ermittelt?Was ist damit:
Der Compiler muss Code für eine Instanziierung von generieren
myfunc
. Wie sollte dieser Code aussehen? Wie können wir diesen Code statisch generieren, wenn wir den Typ nicht kennen?A1
beim Kompilieren ?Schlimmer noch, was ist, wenn sich zur Laufzeit herausstellt
n1 != n2
, dass!std::is_same<decltype(A1), decltype(A2)>()
? In diesem Fall sollte der Aufruf vonmyfunc
nicht einmal kompiliert werden , da der Abzug des Vorlagentyps fehlschlagen sollte! Wie könnten wir dieses Verhalten möglicherweise zur Laufzeit emulieren?Grundsätzlich bewegt sich C ++ in die Richtung, immer mehr Entscheidungen in die Kompilierungszeit zu verschieben : Generierung von Vorlagencode,
constexpr
Funktionsbewertung usw. In der Zwischenzeit war C99 damit beschäftigt, traditionell Entscheidungen zur Kompilierungszeit (z. B.sizeof
) in die Laufzeit zu verschieben . Ist es in diesem Sinne überhaupt sinnvoll, Anstrengungen zu unternehmen , um VLAs im C99-Stil in C ++ zu integrieren?Wie jeder andere Antwortende bereits betont hat, bietet C ++ viele Heap-Zuweisungsmechanismen (
std::unique_ptr<int[]> A = new int[n];
oderstd::vector<int> A(n);
die offensichtlichen), wenn Sie wirklich die Idee vermitteln möchten: "Ich habe keine Ahnung, wie viel RAM ich möglicherweise benötige." Und C ++ bietet ein geschicktes Modell für die Ausnahmebehandlung, um die unvermeidliche Situation zu bewältigen, dass die benötigte RAM-Größe größer ist als die vorhandene RAM-Größe. Aber hoffentlich gibt Ihnen diese Antwort eine gute Vorstellung davon, warum VLAs im C99-Stil nicht gut zu C ++ passen - und nicht einmal wirklich gut zu C99. ;)Weitere Informationen zu diesem Thema finden Sie in N3810 "Alternativen für Array-Erweiterungen" , Bjarne Stroustrups Artikel über VLAs vom Oktober 2013. Bjarnes POV unterscheidet sich sehr von meinem; N3810 konzentriert sich mehr darauf, eine gute C ++ - Ish- Syntax für die Dinge zu finden und die Verwendung von Raw-Arrays in C ++ zu unterbinden, während ich mich mehr auf die Auswirkungen auf die Metaprogrammierung und das Typensystem konzentrierte. Ich weiß nicht, ob er die Auswirkungen der Metaprogrammierung / des Typensystems für gelöst, lösbar oder nur uninteressant hält.
Ein guter Blog-Beitrag, der viele dieser Punkte trifft, ist "Legitime Verwendung von Arrays mit variabler Länge" (Chris Wellons, 27.10.2019).
quelle
alloca()
sollten stattdessen in C99 standardisiert worden sein. VLAs sind das, was passiert, wenn ein Standardkomitee vor Implementierungen herausspringt und nicht umgekehrt.*
ist optional, Sie können (und sollten) schreibenint A[][n]
; (3) Sie können das Typsystem verwenden, ohne tatsächlich VLAs zu deklarieren. Beispielsweise kann eine Funktion ein Array von variabel modifiziertem Typ akzeptieren und mit Nicht-VLA-2D-Arrays mit unterschiedlichen Abmessungen aufgerufen werden. Sie machen jedoch im letzten Teil Ihres Beitrags gültige Punkte.n
in Ihrem Testfall und wie groß war Ihr Stapel? Ich schlage vor, Sie versuchen, einen Wert einzugeben, dern
mindestens so groß ist wie die Größe Ihres Stapels. (Und wenn es für den Benutzer keine Möglichkeit gibt, den Wert vonn
in Ihrem Programm zu steuern , dann schlage ich vor, dass Sie den Maximalwert vonn
direkt in die Deklaration übertragen: deklarierenint A[1000]
oder was auch immer Sie benötigen. VLAs sind nur notwendig und nur gefährlich. wenn der Maximalwert vonn
nicht durch eine kleine Kompilierungszeitkonstante begrenzt ist.)Sie können jederzeit alloca () verwenden, um zur Laufzeit Speicher auf dem Stapel zuzuweisen, wenn Sie dies wünschen:
Die Zuweisung auf dem Stapel bedeutet, dass er automatisch freigegeben wird, wenn der Stapel abgewickelt wird.
Kurzer Hinweis: Wie in der Mac OS X-Manpage für alloca (3) erwähnt, "ist die alloca () -Funktion maschinen- und compilerabhängig; ihre Verwendung wird nicht empfohlen." Nur damit du es weißt.
quelle
if (!p) { p = alloca(strlen(foo)+1); strcpy(p, foo); }
Dies ist mit VLAs nicht möglich, gerade wegen ihres Blockumfangs.C
ähnliche Lösung und nicht wirklichC++
.In meiner eigenen Arbeit habe ich festgestellt, dass es mir jedes Mal, wenn ich etwas wie automatische Arrays mit variabler Länge oder alloca () wollte, egal war, dass sich der Speicher physisch auf dem CPU-Stapel befand, nur dass er von dort stammte Einige Stapelzuweiser, die keine langsamen Fahrten zum allgemeinen Haufen verursachten. Ich habe also ein Pro-Thread-Objekt, das Speicher besitzt, aus dem Puffer mit variabler Größe gepusht / eingeblendet werden können. Auf einigen Plattformen lasse ich dies über mmu wachsen. Andere Plattformen haben eine feste Größe (normalerweise wird dies auch von einem CPU-Stapel mit fester Größe begleitet, da keine mmu vorhanden sind). Eine Plattform, mit der ich arbeite (eine Handheld-Spielekonsole), hat ohnehin einen wertvollen kleinen CPU-Stapel, da sie sich in einem knappen, schnellen Speicher befindet.
Ich sage nicht, dass es niemals erforderlich ist, Puffer variabler Größe auf den CPU-Stapel zu verschieben. Ehrlich gesagt war ich überrascht, als ich herausfand, dass dies kein Standard war, da das Konzept sicherlich gut genug in die Sprache passt. Für mich sind die Anforderungen "variable Größe" und "muss sich physisch auf dem CPU-Stapel befinden" jedoch nie zusammengekommen. Es ging um Geschwindigkeit, also habe ich meinen eigenen "parallelen Stapel für Datenpuffer" erstellt.
quelle
Es gibt Situationen, in denen das Zuweisen von Heapspeicher im Vergleich zu den ausgeführten Vorgängen sehr teuer ist. Ein Beispiel ist die Matrixmathematik. Wenn Sie mit kleinen Matrizen arbeiten, z. B. 5 bis 10 Elemente, und viel rechnen, ist der Malloc-Overhead sehr bedeutend. Gleichzeitig erscheint es sehr verschwenderisch und unflexibel, die Größe zu einer Kompilierungszeitkonstante zu machen.
Ich denke, dass C ++ an sich so unsicher ist, dass das Argument, "nicht mehr unsichere Funktionen hinzuzufügen", nicht sehr stark ist. Auf der anderen Seite, da C ++ wohl die laufzeiteffizienteste Programmiersprachenfunktion ist, die es immer nützlicher macht: Leute, die leistungskritische Programme schreiben, werden C ++ in großem Umfang verwenden und sie benötigen so viel Leistung wie möglich. Das Verschieben von Sachen vom Haufen zum Stapel ist eine solche Möglichkeit. Das Reduzieren der Anzahl von Heap-Blöcken ist eine andere. Das Zulassen von VLAs als Objektmitglieder wäre eine Möglichkeit, dies zu erreichen. Ich arbeite an einem solchen Vorschlag. Die Implementierung ist zwar etwas kompliziert, scheint aber durchaus machbar.
quelle
Scheint, dass es in C ++ 14 verfügbar sein wird:
https://en.wikipedia.org/wiki/C%2B%2B14#Runtime-sized_one_dimensional_arrays
Update: Es hat es nicht in C ++ 14 geschafft.
quelle
Dies wurde für die Aufnahme in C ++ / 1x in Betracht gezogen, wurde jedoch gestrichen (dies ist eine Korrektur zu dem, was ich zuvor gesagt habe).
In C ++ wäre es sowieso weniger nützlich, da wir
std::vector
diese Rolle bereits ausfüllen müssen.quelle
std::vector
anstatt beispielsweisealloca()
.Verwenden Sie dazu std :: vector. Zum Beispiel:
Der Speicher wird auf dem Heap zugewiesen, dies hat jedoch nur einen geringen Leistungsnachteil. Darüber hinaus ist es ratsam, dem Stapel keine großen Datenblöcke zuzuweisen, da seine Größe eher begrenzt ist.
quelle
std::vector<int> values(n);
? Durch die Verwendungresize
nach dem Bau verbieten Sie unbewegliche Typen.C99 erlaubt VLA. Und es gibt einige Einschränkungen für die Deklaration von VLA. Einzelheiten finden Sie in 6.7.5.2 des Standards. C ++ verbietet VLA. Aber g ++ erlaubt es.
quelle
Arrays wie dieses sind Teil von C99, aber nicht Teil von Standard-C ++. Wie andere gesagt haben, ist ein Vektor immer eine viel bessere Lösung, weshalb Arrays mit variabler Größe wahrscheinlich nicht im C ++ - Standard (oder im vorgeschlagenen C ++ 0x-Standard) enthalten sind.
Übrigens, für Fragen zum "Warum" des C ++ - Standards ist die moderierte Usenet-Newsgroup comp.std.c ++ der richtige Ort.
quelle
Wenn Sie den Wert zur Kompilierungszeit kennen, können Sie Folgendes tun:
Bearbeiten: Sie können einen Vektor erstellen, der einen Stapelzuweiser (Alloca) verwendet, da der Allokator ein Vorlagenparameter ist.
quelle
Ich habe eine Lösung, die tatsächlich für mich funktioniert hat. Ich wollte wegen der Fragmentierung einer Routine, die viele Male ausgeführt werden musste, keinen Speicher zuweisen. Die Antwort ist äußerst gefährlich. Verwenden Sie sie daher auf eigenes Risiko. Sie nutzt jedoch die Montage, um Platz auf dem Stapel zu reservieren. In meinem folgenden Beispiel wird ein Zeichenarray verwendet (offensichtlich würde eine Variable anderer Größe mehr Speicher benötigen).
Die Gefahren hier sind vielfältig, aber ich werde einige erklären: 1. Eine Änderung der Variablengröße auf halbem Weg würde die Stapelposition zerstören. 2. Ein Überschreiten der Array-Grenzen würde andere Variablen und möglichen Code zerstören. 3. Dies funktioniert nicht in einem 64-Bit build ... brauche eine andere Assembly für diese (aber ein Makro könnte dieses Problem lösen). 4. Compilerspezifisch (kann Probleme beim Wechseln zwischen Compilern haben). Ich habe es nicht versucht, also weiß ich es wirklich nicht.
quelle
esp
und seine Zugriffe auf den Stapel anpassen, aber in z. B. GCC werden Sie es einfach vollständig brechen - zumindest wenn Sie Optimierungen verwenden und-fomit-frame-pointer
insbesondere.