Ausblenden von Informationen
Was ist der Vorteil der Rückgabe eines Zeigers auf eine Struktur im Vergleich zur Rückgabe der gesamten Struktur in der return-Anweisung der Funktion?
Am häufigsten werden Informationen versteckt . C bietet beispielsweise nicht die Möglichkeit, Felder als struct
privat zu kennzeichnen, geschweige denn Methoden für den Zugriff darauf bereitzustellen.
Wenn Sie also gewaltsam verhindern möchten, dass Entwickler den Inhalt eines Pointees sehen und manipulieren können, FILE
besteht die einzige Möglichkeit darin, zu verhindern, dass sie seiner Definition ausgesetzt werden, indem Sie den Zeiger als undurchsichtig behandeln, dessen Pointee-Größe und Definition sind der Außenwelt unbekannt. Die Definition von FILE
wird dann nur für diejenigen sichtbar sein, die die Operationen implementieren, für die ihre Definition erforderlich ist fopen
, während nur die Strukturdeklaration für den öffentlichen Header sichtbar ist.
Binäre Kompatibilität
Das Ausblenden der Strukturdefinition kann auch dazu beitragen, Freiraum für die Wahrung der Binärkompatibilität in dylib-APIs zu schaffen. Es ermöglicht den Implementierern der Bibliothek, die Felder in der undurchsichtigen Struktur zu ändern, ohne die Binärkompatibilität mit denen zu beeinträchtigen, die die Bibliothek verwenden, da die Art ihres Codes nur wissen muss, was sie mit der Struktur tun können, nicht wie groß sie ist oder welche Felder es hat.
Zum Beispiel kann ich einige alte Programme ausführen, die zur Zeit von Windows 95 erstellt wurden (nicht immer perfekt, aber überraschenderweise funktionieren noch viele). Möglicherweise verwendete ein Teil des Codes für diese alten Binärdateien undurchsichtige Zeiger auf Strukturen, deren Größe und Inhalt sich seit der Windows 95-Ära geändert haben. Die Programme funktionieren jedoch weiterhin in neuen Versionen von Fenstern, da sie nicht dem Inhalt dieser Strukturen ausgesetzt waren. Bei der Arbeit an einer Bibliothek, bei der die Binärkompatibilität wichtig ist, darf sich das, was dem Client nicht zugänglich ist, im Allgemeinen ändern, ohne die Abwärtskompatibilität zu beeinträchtigen.
Effizienz
Die Rückgabe einer vollständigen Struktur, die NULL ist, ist vermutlich schwieriger oder weniger effizient. Ist das ein triftiger Grund?
Es ist in der Regel weniger effizient, vorausgesetzt, der Typ kann praktisch in den Stapel passen und zugewiesen werden, es sei denn, im Hintergrund wird in der Regel ein viel weniger verallgemeinerter Speicherallokator verwendet als malloc
ein bereits zugewiesener Poolspeicher mit fester Größe und nicht mit variabler Größe. In diesem Fall handelt es sich höchstwahrscheinlich um einen Kompromiss bei der Sicherheit, der es den Bibliotheksentwicklern ermöglicht, Invarianten (konzeptionelle Garantien) im Zusammenhang mit zu pflegen FILE
.
Zumindest aus Performance- fopen
Gründen ist es kein so gültiger Grund, einen Zeiger zurückgeben zu lassen, da der einzige Grund, der zurückgegeben NULL
wird, darin besteht, dass eine Datei nicht geöffnet werden konnte. Dies würde ein außergewöhnliches Szenario optimieren, um alle gängigen Ausführungspfade zu verlangsamen. In einigen Fällen kann es einen gültigen Produktivitätsgrund geben, Entwürfe einfacher zu gestalten, damit sie Zeiger zurückgeben, damit NULL
sie unter bestimmten Bedingungen nachträglich zurückgegeben werden können.
Bei Dateioperationen ist der Aufwand im Vergleich zu den Dateioperationen relativ gering, und das manuelle Vorgehen fclose
muss ohnehin nicht vermieden werden. Es ist also nicht so, als könnten wir dem Client den Aufwand ersparen, die Ressource freizugeben (zu schließen), indem wir die Definition der Datei offenlegen FILE
und sie als Wert zurückgeben, fopen
oder wenn wir angesichts der relativen Kosten der Dateioperationen selbst einen erheblichen Leistungsschub erwarten, um eine Heap-Zuweisung zu vermeiden .
Hotspots und Fixes
In anderen Fällen habe ich jedoch eine Menge verschwenderischen C-Codes in Legacy-Codebasen mit Hotspots in malloc
und unnötigen obligatorischen Cache-Fehlern profiliert, da diese Vorgehensweise mit undurchsichtigen Zeigern zu häufig angewendet wurde und zu viele Dinge unnötigerweise auf dem Heap zugewiesen wurden, manchmal in große Schleifen.
Eine alternative Methode, die ich stattdessen verwende, besteht darin, Strukturdefinitionen offen zu legen, auch wenn der Client nicht dazu gedacht ist, sie zu manipulieren, indem er einen Namenskonventionsstandard verwendet, um mitzuteilen, dass niemand anderes die Felder berühren sollte:
struct Foo
{
/* priv_* indicates that you shouldn't tamper with these fields! */
int priv_internal_field;
int priv_other_one;
};
struct Foo foo_create(void);
void foo_destroy(struct Foo* foo);
void foo_something(struct Foo* foo);
Wenn es in Zukunft Probleme mit der Binärkompatibilität gibt, habe ich es für gut genug befunden, nur überflüssig zusätzlichen Speicherplatz für zukünftige Zwecke zu reservieren:
struct Foo
{
/* priv_* indicates that you shouldn't tamper with these fields! */
int priv_internal_field;
int priv_other_one;
/* reserved for possible future uses (emergency backup plan).
currently just set to null. */
void* priv_reserved;
};
Dieser reservierte Speicherplatz ist etwas verschwenderisch, kann aber lebensrettend sein, wenn wir in Zukunft feststellen, dass wir weitere Daten hinzufügen müssen, Foo
ohne die Binärdateien zu beschädigen, die unsere Bibliothek verwenden.
Meiner Meinung nach ist das Ausblenden von Informationen und die Binärkompatibilität in der Regel der einzige vernünftige Grund, um neben Strukturen mit variabler Länge nur die Heap-Zuweisung zuzulassen (was immer erforderlich wäre oder zumindest etwas umständlich zu verwenden, wenn der Client eine Zuweisung vornehmen müsste Speicher auf dem Stapel in einer VLA-Art und Weise, um die VLS zuzuweisen). Sogar große Strukturen sind oft günstiger, wenn die Software viel mehr mit dem Hot-Memory auf dem Stack arbeitet. Und selbst wenn sie bei der Wertschöpfung nicht günstiger zu haben wären, könnte man dies einfach tun:
int foo_create(struct Foo* foo);
...
/* In the client code: */
struct Foo foo;
if (foo_create(&foo))
{
foo_something(&foo);
foo_destroy(&foo);
}
... Foo
ohne die Möglichkeit einer überflüssigen Kopie vom Stapel zu initialisieren . Oder der Kunde hat sogar die Freiheit, Foo
auf dem Haufen zuzuteilen, wenn er dies aus irgendeinem Grund möchte.