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.
quelle
void examine data(const struct blob)
ist falsch.gettimeofday
) Verwendung Zeiger statt, und die Leute nehmen das als Beispiel.Antworten:
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.
quelle
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
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.
quelle
int &bar() { int f; int &j(f); return j;};
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:
Kompilieren Sie es mit vollständigen Optimierungen
Schauen Sie sich die Baugruppe an:
Das bekommen wir:
Mit Ausnahme der
nopl
Padsgive_two_doubles()
hat es 27 Bytes, währendgive_point()
es 29 Bytes hat. Auf der anderen Seitegive_point()
ergibt sich eine Anweisung weniger alsgive_two_doubles()
Interessant ist, dass wir feststellen, dass der Compiler
mov
in die schnelleren SSE2-Variantenmovapd
und optimieren konntemovsd
. Außerdem werdengive_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!
quelle
const
stä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.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?).
quelle
Eine Sache, die die Leute hier bisher vergessen haben zu erwähnen (oder ich habe es übersehen), ist, dass Strukturen normalerweise eine Polsterung haben!
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.
quelle
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.
quelle
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.
quelle
Hier ist etwas, das niemand erwähnt hat:
Mitglieder von a
const struct
sindconst
, aber wenn dieses Mitglied ein Zeiger (wiechar *
) ist, wird eschar *const
eher als das, wasconst char *
wir wirklich wollen. Natürlich können wir davon ausgehen, dass diesconst
eine 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 mittypedef
Zeigern habe. Daher bleiben die meisten Leute bei nur zwei Parametern (oder, in diesem Fall wahrscheinlicher, bei der Verwendung einer Zeichenfolgenbibliothek).quelle
struct const_blob
Lösung ist, dass selbst wennconst_blob
Mitgliederblob
nur von der "indirekten Konstanz" abweichen , Typenstruct blob*
zu astruct const_blob*
für die Zwecke einer strengen Aliasing-Regel als unterschiedlich betrachtet werden. Wenn Code ablob*
in a umwandelt, macht folglichconst_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). .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:
Ich verwende den folgenden C-Code, um die obige Aussage zu überprüfen:
Verwenden Sie "gcc -S", um eine Assembly für diesen C-Code zu generieren:
Der Stapel vor dem Aufruf erstellen:
Der Stack direkt nach dem Aufruf von create:
quelle
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.
quelle