Gibt es Nachteile beim Übergeben von Strukturen nach Wert in C, anstatt einen Zeiger zu übergeben?


157

Gibt es Nachteile beim Übergeben von Strukturen nach Wert in C, anstatt einen Zeiger zu übergeben?

Wenn die Struktur groß ist, gibt es offensichtlich den leistungsfähigen Aspekt des Kopierens vieler Daten, aber für eine kleinere Struktur sollte dies im Grunde das gleiche sein wie das Übergeben mehrerer Werte an eine Funktion.

Es ist vielleicht noch interessanter, wenn es als Rückgabewerte verwendet wird. C hat nur einzelne Rückgabewerte von Funktionen, aber Sie benötigen häufig mehrere. Eine einfache Lösung besteht also darin, sie in eine Struktur zu setzen und diese zurückzugeben.

Gibt es Gründe dafür oder dagegen?

Da es möglicherweise nicht jedem klar ist, wovon ich hier spreche, gebe ich ein einfaches Beispiel.

Wenn Sie in C programmieren, werden Sie früher oder später Funktionen schreiben, die folgendermaßen aussehen:

void examine_data(const char *ptr, size_t len)
{
    ...
}

char *p = ...;
size_t l = ...;
examine_data(p, l);

Das ist kein Problem. Das einzige Problem ist, dass Sie mit Ihrem Kollegen vereinbaren müssen, in welcher Reihenfolge die Parameter sein sollen, damit Sie in allen Funktionen dieselbe Konvention verwenden.

Aber was passiert, wenn Sie dieselbe Art von Informationen zurückgeben möchten? Normalerweise erhalten Sie so etwas:

char *get_data(size_t *len);
{
    ...
    *len = ...datalen...;
    return ...data...;
}
size_t len;
char *p = get_data(&len);

Dies funktioniert gut, ist aber viel problematischer. Ein Rückgabewert ist ein Rückgabewert, außer dass dies in dieser Implementierung nicht der Fall ist. Aus dem oben Gesagten lässt sich nicht ableiten, dass die Funktion get_data nicht prüfen darf, auf was len zeigt. Und nichts lässt den Compiler überprüfen, ob tatsächlich ein Wert über diesen Zeiger zurückgegeben wird. Wenn also im nächsten Monat jemand anderes den Code ändert, ohne ihn richtig zu verstehen (weil er die Dokumentation nicht gelesen hat?), Wird er beschädigt, ohne dass es jemand merkt, oder er stürzt zufällig ab.

Die Lösung, die ich vorschlage, ist die einfache Struktur

struct blob { char *ptr; size_t len; }

Die Beispiele können folgendermaßen umgeschrieben werden:

void examine_data(const struct blob data)
{
    ... use data.tr and data.len ...
}

struct blob = { .ptr = ..., .len = ... };
examine_data(blob);

struct blob get_data(void);
{
    ...
    return (struct blob){ .ptr = ...data..., .len = ...len... };
}
struct blob data = get_data();

Aus irgendeinem Grund denke ich, dass die meisten Leute instinktiv untersuchen_data einen Zeiger auf einen Struktur-Blob nehmen lassen würden, aber ich verstehe nicht warum. Es bekommt immer noch einen Zeiger und eine ganze Zahl, es ist nur viel klarer, dass sie zusammenpassen. Und im Fall get_data ist es unmöglich, auf die zuvor beschriebene Weise durcheinander zu kommen, da es keinen Eingabewert für die Länge gibt und es eine zurückgegebene Länge geben muss.


Für was es wert ist, void examine data(const struct blob)ist falsch.
Chris Lutz

Vielen Dank, es wurde geändert, um einen Variablennamen aufzunehmen.
dkagedal

1
"Aus dem oben Gesagten lässt sich nicht ableiten, dass die Funktion get_data nicht prüfen darf, auf was len zeigt. Und nichts lässt den Compiler überprüfen, ob tatsächlich ein Wert über diesen Zeiger zurückgegeben wird." - das macht für mich überhaupt keinen Sinn (vielleicht weil Ihr Beispiel ungültiger Code ist, weil die letzten beiden Zeilen außerhalb einer Funktion erscheinen); können Sie bitte näher darauf eingehen?
Adam Spires

2
Die beiden Zeilen unter der Funktion veranschaulichen, wie die Funktion aufgerufen wird. Die Funktionssignatur gibt keinen Hinweis darauf, dass die Implementierung nur in den Zeiger schreiben soll. Und der Compiler kann nicht wissen, dass er überprüfen soll, ob ein Wert in den Zeiger geschrieben wurde, sodass der Rückgabewertmechanismus nur in der Dokumentation beschrieben werden kann.
dkagedal

1
Der Hauptgrund, warum Menschen dies in C nicht öfter tun, ist historisch. Vor C89, Sie konnte nicht übergeben oder Rückkehr structs nach Wert, so alle Systemschnittstellen , die C89 predate und sollte logisch , es zu tun (wie gettimeofday) Verwendung Zeiger statt, und die Leute nehmen das als Beispiel.
zwol

Antworten:


202

Für kleine Strukturen (z. B. point, rect) ist das Übergeben von Werten durchaus akzeptabel. Abgesehen von der Geschwindigkeit gibt es noch einen weiteren Grund, warum Sie vorsichtig sein sollten, große Strukturen nach Wert zu übergeben / zurückzugeben: Stapelplatz.

Viele C-Programme sind für eingebettete Systeme vorgesehen, bei denen der Speicher knapp ist und die Stapelgrößen in KB oder sogar Bytes gemessen werden können. Wenn Sie Strukturen nach Wert übergeben oder zurückgeben, werden Kopien dieser Strukturen platziert der Stapel, der möglicherweise die Situation verursacht, dass diese Site nach ... benannt ist.

Wenn ich eine Anwendung sehe, die eine übermäßige Stapelauslastung zu haben scheint, sind Strukturen, die als Wert übergeben werden, eines der Dinge, nach denen ich zuerst suche.


2
"Wenn Sie Strukturen nach Wert übergeben oder zurückgeben, werden Kopien dieser Strukturen auf dem Stapel abgelegt." Ich würde braindead jede Toolchain nennen, die dies tut. Ja, es ist traurig, dass so viele es tun werden, aber es ist nichts, was der C-Standard verlangt. Ein vernünftiger Compiler wird alles optimieren.
Stellen Sie Monica

1
@ KubaOber Deshalb wird das nicht oft gemacht: stackoverflow.com/questions/552134/…
Roddy

1
Gibt es eine definitive Linie, die eine kleine Struktur von einer großen Struktur trennt?
Josie Thompson

63

Ein Grund, dies nicht zu tun, der nicht erwähnt wurde, ist, dass dies ein Problem verursachen kann, bei dem die Binärkompatibilität wichtig ist.

Abhängig vom verwendeten Compiler können Strukturen je nach Compileroptionen / -implementierung über den Stapel oder die Register übergeben werden

Siehe: http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html

-fpcc-struct-return

-freg-struct-return

Wenn zwei Compiler nicht übereinstimmen, können Dinge in die Luft jagen. Es ist unnötig zu erwähnen, dass die Hauptgründe, warum dies nicht getan wird, der Stapelverbrauch und die Leistungsgründe sind.


4
Dies war die Art von Antwort, nach der ich gesucht habe.
dkagedal

2
Stimmt, aber diese Optionen beziehen sich nicht auf die Wertübergabe. Sie beziehen sich auf die Rückgabe von Strukturen, was eine ganz andere Sache ist. Das Zurücksenden von Dingen als Referenz ist normalerweise eine sichere Art, sich in beide Füße zu schießen. int &bar() { int f; int &j(f); return j;};
Roddy

19

Um diese Frage wirklich zu beantworten, muss man tief in das Versammlungsland graben:

(Im folgenden Beispiel wird gcc unter x86_64 verwendet. Jeder kann andere Architekturen wie MSVC, ARM usw. hinzufügen.)

Lassen Sie uns unser Beispielprogramm haben:

// foo.c

typedef struct
{
    double x, y;
} point;

void give_two_doubles(double * x, double * y)
{
    *x = 1.0;
    *y = 2.0;
}

point give_point()
{
    point a = {1.0, 2.0};
    return a;
}

int main()
{
    return 0;
}

Kompilieren Sie es mit vollständigen Optimierungen

gcc -Wall -O3 foo.c -o foo

Schauen Sie sich die Baugruppe an:

objdump -d foo | vim -

Das bekommen wir:

0000000000400480 <give_two_doubles>:
    400480: 48 ba 00 00 00 00 00    mov    $0x3ff0000000000000,%rdx
    400487: 00 f0 3f 
    40048a: 48 b8 00 00 00 00 00    mov    $0x4000000000000000,%rax
    400491: 00 00 40 
    400494: 48 89 17                mov    %rdx,(%rdi)
    400497: 48 89 06                mov    %rax,(%rsi)
    40049a: c3                      retq   
    40049b: 0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000004004a0 <give_point>:
    4004a0: 66 0f 28 05 28 01 00    movapd 0x128(%rip),%xmm0
    4004a7: 00 
    4004a8: 66 0f 29 44 24 e8       movapd %xmm0,-0x18(%rsp)
    4004ae: f2 0f 10 05 12 01 00    movsd  0x112(%rip),%xmm0
    4004b5: 00 
    4004b6: f2 0f 10 4c 24 f0       movsd  -0x10(%rsp),%xmm1
    4004bc: c3                      retq   
    4004bd: 0f 1f 00                nopl   (%rax)

Mit Ausnahme der noplPads give_two_doubles()hat es 27 Bytes, während give_point()es 29 Bytes hat. Auf der anderen Seite give_point()ergibt sich eine Anweisung weniger alsgive_two_doubles()

Interessant ist, dass wir feststellen, dass der Compiler movin die schnelleren SSE2-Varianten movapdund optimieren konnte movsd. Außerdem werden give_two_doubles()Daten tatsächlich in den Speicher hinein- und aus diesem heraus verschoben, was die Dinge verlangsamt.

Anscheinend ist ein Großteil davon in eingebetteten Umgebungen möglicherweise nicht anwendbar (wo heutzutage das Spielfeld für C die meiste Zeit ist). Ich bin kein Assembler-Assistent, daher sind Kommentare willkommen!


6
Das Zählen der Anzahl der Anweisungen ist nicht allzu interessant, es sei denn, Sie können einen großen Unterschied zeigen oder interessantere Aspekte wie die Anzahl der schwer vorhersehbaren Sprünge usw. zählen. Die tatsächlichen Leistungseigenschaften sind viel subtiler als die Anzahl der Anweisungen .
dkagedal

6
@dkagedal: Richtig. Rückblickend denke ich, dass meine eigene Antwort sehr schlecht geschrieben wurde. Obwohl ich mich nicht sehr auf die Anzahl der Anweisungen konzentriert habe (keine Ahnung, was Ihnen diesen Eindruck vermittelt hat: P), war der eigentliche Punkt, den Sie machen sollten, dass die Übergabe von struct by value der Übergabe als Referenz für kleine Typen vorzuziehen ist. Auf jeden Fall wird das Übergeben von Werten bevorzugt, da es einfacher ist (kein lebenslanges Jonglieren, keine Notwendigkeit, sich Sorgen zu machen, dass jemand Ihre Daten ändert oder constständig), und ich fand, dass das Kopieren von Wertüberschreitungen nicht viel Leistungseinbußen (wenn nicht Gewinn) mit sich bringt im Gegensatz zu dem, was viele glauben könnten.
kizzx2

15

Eine einfache Lösung besteht darin, einen Fehlercode als Rückgabewert und alles andere als Parameter in der Funktion zurückzugeben.
Dieser Parameter kann natürlich eine Struktur sein, sieht jedoch keinen besonderen Vorteil darin, diesen Wert zu übergeben. Senden Sie einfach einen Zeiger.
Das Übergeben einer Struktur nach Wert ist gefährlich. Sie müssen sehr vorsichtig sein, was Sie übergeben. Denken Sie daran, dass es in C keinen Kopierkonstruktor gibt. Wenn einer der Strukturparameter ein Zeiger ist, wird der Zeigerwert kopiert. Dies kann sehr verwirrend und schwierig sein pflegen.

Nur um die Antwort zu vervollständigen (volle Anerkennung für Roddy ), ist die Stapelverwendung ein weiterer Grund, warum die Struktur nicht nach Wert übergeben wird. Glauben Sie mir, dass das Debuggen des Stapelüberlaufs eine echte PITA ist.

Zum Kommentieren wiederholen:

Das Übergeben von struct per Zeiger bedeutet, dass eine Entität Eigentümer dieses Objekts ist und genau weiß, was und wann freigegeben werden soll. Wenn Sie struct by value übergeben, werden versteckte Verweise auf die internen Daten von struct (Zeiger auf andere Strukturen usw.) erstellt. Dies ist schwer zu pflegen (möglich, aber warum?).


6
Aber das Übergeben eines Zeigers ist nicht "gefährlicher", nur weil Sie ihn in eine Struktur einfügen, also kaufe ich ihn nicht.
dkagedal

Ein guter Punkt beim Kopieren einer Struktur, die einen Zeiger enthält. Dieser Punkt ist möglicherweise nicht sehr offensichtlich. Für diejenigen, die nicht wissen, worauf er sich bezieht, führen Sie eine Suche nach tiefer Kopie gegen flache Kopie durch.
Zooropa

1
Eine der C-Funktionskonventionen besteht darin, dass die Ausgabeparameter zuerst vor den Eingabeparametern aufgelistet werden, z. B. int func (char * out, char * in);
Zooropa

Sie meinen, wie zum Beispiel getaddrinfo () den Ausgabeparameter zuletzt setzt? :-) Es gibt tausend Konventionen, und Sie können wählen, was Sie wollen.
dkagedal

10

Eine Sache, die die Leute hier bisher vergessen haben zu erwähnen (oder ich habe es übersehen), ist, dass Strukturen normalerweise eine Polsterung haben!

struct {
  short a;
  char b;
  short c;
  char d;
}

Jedes Zeichen ist 1 Byte, jeder Kurzschluss ist 2 Byte. Wie groß ist die Struktur? Nein, es sind keine 6 Bytes. Zumindest nicht auf häufiger verwendeten Systemen. Auf den meisten Systemen ist es 8. Das Problem ist, dass die Ausrichtung nicht konstant ist, sondern systemabhängig, sodass dieselbe Struktur auf verschiedenen Systemen unterschiedliche Ausrichtungen und Größen aufweist.

Das Auffüllen verschlingt nicht nur Ihren Stapel weiter, sondern erhöht auch die Unsicherheit, dass das Auffüllen nicht im Voraus vorhergesagt werden kann, es sei denn, Sie wissen, wie Ihr System auffüllt, und sehen sich dann jede einzelne Struktur in Ihrer App an und berechnen die Größe dafür. Das Übergeben eines Zeigers nimmt eine vorhersehbare Menge an Platz ein - es gibt keine Unsicherheit. Die Größe eines Zeigers ist für das System bekannt, sie ist immer gleich, unabhängig davon, wie die Struktur aussieht, und die Zeigergrößen werden immer so ausgewählt, dass sie ausgerichtet sind und keine Auffüllung benötigen.


2
Ja, aber das Auffüllen existiert ohne Abhängigkeit davon, ob die Struktur als Wert oder als Referenz übergeben wird.
Ilya

2
@dkagedal: Welchen Teil von "verschiedenen Größen auf verschiedenen Systemen" hast du nicht verstanden? Nur weil es auf Ihrem System so ist, gehen Sie davon aus, dass es für jedes andere System dasselbe sein muss - genau deshalb sollten Sie keinen Wert übergeben. Das Beispiel wurde so geändert, dass es auch auf Ihrem System fehlschlägt.
Mecki

2
Ich denke, Meckis Kommentare zum Strukturauffüllen sind insbesondere für eingebettete Systeme relevant, bei denen die Stapelgröße ein Problem sein kann.
Zooropa

1
Ich denke, die Kehrseite des Arguments ist, dass wenn Ihre Struktur eine einfache Struktur ist (die einige primitive Typen enthält), die Übergabe von Werten es dem Compiler ermöglicht, sie mithilfe von Registern zu jonglieren - während, wenn Sie Zeiger verwenden, die Dinge in enden die Erinnerung, die langsamer ist. Das wird ziemlich niedrig und hängt ziemlich stark von Ihrer Zielarchitektur ab, ob einer dieser Leckerbissen eine Rolle spielt.
kizzx2

1
Sofern Ihre Struktur nicht winzig ist oder Ihre CPU viele Register hat (und Intel-CPUs nicht), landen die Daten auf dem Stapel und das ist auch Speicher und so schnell / langsam wie jeder andere Speicher. Ein Zeiger hingegen ist immer klein und nur ein Zeiger, und der Zeiger selbst landet normalerweise immer in einem Register, wenn er häufiger verwendet wird.
Mecki

9

Ich denke, dass Ihre Frage die Dinge ziemlich gut zusammengefasst hat.

Ein weiterer Vorteil der Übergabe von Strukturen nach Wert besteht darin, dass der Speicherbesitz explizit ist. Es ist nicht verwunderlich, ob die Struktur vom Heap stammt und wer für die Freigabe verantwortlich ist.


9

Ich würde sagen, dass das Übergeben von (nicht zu großen) Strukturen nach Wert, sowohl als Parameter als auch als Rückgabewerte, eine absolut legitime Technik ist. Man muss natürlich darauf achten, dass die Struktur entweder ein POD-Typ ist oder die Kopiersemantik gut spezifiziert ist.

Update: Entschuldigung, ich hatte meine C ++ - Denkkapsel aktiviert. Ich erinnere mich an eine Zeit, als es in C nicht legal war, eine Struktur von einer Funktion zurückzugeben, aber dies hat sich wahrscheinlich seitdem geändert. Ich würde immer noch sagen, dass es gültig ist, solange alle Compiler, die Sie verwenden möchten, die Praxis unterstützen.


Beachten Sie, dass meine Frage C betraf, nicht C ++.
dkagedal

Es ist gültig, Struktur von der Funktion zurückzugeben, nur nicht nützlich :)
Ilya

1
Ich mag Llyas Vorschlag, die Rückgabe als Fehlercode und Parameter für die Rückgabe von Daten aus der Funktion zu verwenden.
Zooropa

8

Hier ist etwas, das niemand erwähnt hat:

void examine_data(const char *c, size_t l)
{
    c[0] = 'l'; // compiler error
}

void examine_data(const struct blob blob)
{
    blob.ptr[0] = 'l'; // perfectly legal, quite likely to blow up at runtime
}

Mitglieder von a const structsind const, aber wenn dieses Mitglied ein Zeiger (wie char *) ist, wird es char *consteher als das, was const char *wir wirklich wollen. Natürlich können wir davon ausgehen, dass dies consteine Dokumentation der Absicht ist und dass jeder, der dagegen verstößt, schlechten Code schreibt (was er ist), aber das ist für einige nicht gut genug (insbesondere für diejenigen, die nur vier Stunden damit verbracht haben, die Ursache von a aufzuspüren Absturz).

Die Alternative könnte sein, ein zu erstellen struct const_blob { const char *c; size_t l }und das zu verwenden, aber das ist ziemlich chaotisch - es kommt zu dem gleichen Namensschema-Problem, das ich mit typedefZeigern habe. Daher bleiben die meisten Leute bei nur zwei Parametern (oder, in diesem Fall wahrscheinlicher, bei der Verwendung einer Zeichenfolgenbibliothek).


Ja, es ist vollkommen legal und auch etwas, das Sie manchmal tun möchten. Ich stimme jedoch zu, dass es eine Einschränkung der Strukturlösung ist, dass Sie die Zeiger, auf die sie zeigen, nicht auf const setzen können.
dkagedal

Ein böses Problem bei der struct const_blobLösung ist, dass selbst wenn const_blobMitglieder blobnur von der "indirekten Konstanz" abweichen , Typen struct blob*zu a struct const_blob*für die Zwecke einer strengen Aliasing-Regel als unterschiedlich betrachtet werden. Wenn Code a blob*in a umwandelt, macht folglich const_blob*jedes nachfolgende Schreiben in die zugrunde liegende Struktur unter Verwendung eines Typs alle vorhandenen Zeiger des anderen Typs stillschweigend ungültig, so dass jede Verwendung undefiniertes Verhalten hervorruft (das normalerweise harmlos sein kann, aber tödlich sein kann). .
Supercat

5

Auf Seite 150 des PC Assembly Tutorial unter http://www.drpaulcarter.com/pcasm/ finden Sie eine klare Erklärung, wie C einer Funktion ermöglicht, eine Struktur zurückzugeben:

Mit C kann auch ein Strukturtyp als Rückgabewert einer Funktion verwendet werden. Offensichtlich kann eine Struktur nicht im EAX-Register zurückgegeben werden. Verschiedene Compiler gehen unterschiedlich mit dieser Situation um. Eine gängige Lösung, die Compiler verwenden, besteht darin, die Funktion intern als eine Funktion neu zu schreiben, die einen Strukturzeiger als Parameter verwendet. Der Zeiger wird verwendet, um den Rückgabewert in eine Struktur zu setzen, die außerhalb der aufgerufenen Routine definiert ist.

Ich verwende den folgenden C-Code, um die obige Aussage zu überprüfen:

struct person {
    int no;
    int age;
};

struct person create() {
    struct person jingguo = { .no = 1, .age = 2};
    return jingguo;
}

int main(int argc, const char *argv[]) {
    struct person result;
    result = create();
    return 0;
}

Verwenden Sie "gcc -S", um eine Assembly für diesen C-Code zu generieren:

    .file   "foo.c"
    .text
.globl create
    .type   create, @function
create:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $16, %esp
    movl    8(%ebp), %ecx
    movl    $1, -8(%ebp)
    movl    $2, -4(%ebp)
    movl    -8(%ebp), %eax
    movl    -4(%ebp), %edx
    movl    %eax, (%ecx)
    movl    %edx, 4(%ecx)
    movl    %ecx, %eax
    leave
    ret $4
    .size   create, .-create
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $20, %esp
    leal    -8(%ebp), %eax
    movl    %eax, (%esp)
    call    create
    subl    $4, %esp
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

Der Stapel vor dem Aufruf erstellen:

        +---------------------------+
ebp     | saved ebp                 |
        +---------------------------+
ebp-4   | age part of struct person | 
        +---------------------------+
ebp-8   | no part of struct person  |
        +---------------------------+        
ebp-12  |                           |
        +---------------------------+
ebp-16  |                           |
        +---------------------------+
ebp-20  | ebp-8 (address)           |
        +---------------------------+

Der Stack direkt nach dem Aufruf von create:

        +---------------------------+
        | ebp-8 (address)           |
        +---------------------------+
        | return address            |
        +---------------------------+
ebp,esp | saved ebp                 |
        +---------------------------+

2
Hier gibt es zwei Probleme. Das offensichtlichste ist, dass dies überhaupt nicht beschreibt, "wie C einer Funktion erlaubt, eine Struktur zurückzugeben". Dies beschreibt nur, wie dies auf 32-Bit-x86-Hardware möglich ist. Dies ist eine der am stärksten eingeschränkten Architekturen, wenn man sich die Anzahl der Register usw. ansieht. Das zweite Problem besteht darin, dass C-Compiler Code für die Rückgabe von Werten generieren wird vom ABI vorgegeben (außer für nicht exportierte oder inline-Funktionen). Übrigens sind Inline-Funktionen wahrscheinlich einer der Orte, an denen die Rückgabe von Strukturen am nützlichsten ist.
dkagedal

Danke für die Korrekturen. En.wikipedia.org/wiki/Calling_convention ist eine gute Referenz für eine vollständige Beschreibung der Aufrufkonvention .
Jingguo Yao

@dkagedal: Was wichtig ist, ist nicht nur, dass x86 Dinge auf diese Weise tut, sondern dass es einen "universellen" Ansatz gibt (dh diesen), der es Compilern für jede Plattform ermöglicht, Rückgaben von jedem Strukturtyp zu unterstützen, der nicht 'ist. Es ist so groß, dass es den Stapel sprengt. Während Compiler für viele Plattformen andere effizientere Mittel zur Behandlung einiger Rückgabewerte vom Strukturtyp verwenden, muss die Sprache die Rückgabetypen der Struktur nicht auf diejenigen beschränken, die die Plattform optimal verarbeiten kann.
Supercat

0

Ich möchte nur darauf hinweisen, dass ein Vorteil der Übergabe Ihrer Strukturen als Wert darin besteht, dass ein optimierender Compiler Ihren Code möglicherweise besser optimiert.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.