Masquage de l'information
Quel est l’avantage de renvoyer un pointeur sur une structure plutôt que de renvoyer l’ensemble de la structure dans l’énoncé de retour de la fonction?
Le plus commun est la dissimulation d'informations . C n’a pas, par exemple, la capacité de créer des champs d’un domaine struct
privé, et encore moins de fournir des méthodes pour y accéder.
Donc, si vous voulez empêcher avec force les développeurs de voir et d'altérer le contenu d'une pointe, par exemple FILE
, le seul moyen consiste à les empêcher d'être exposés à sa définition en traitant le pointeur comme un opaque dont la taille et le définition sont inconnus du monde extérieur. La définition de FILE
ne sera alors visible que pour ceux qui implémentent les opérations qui nécessitent sa définition, comme fopen
, tandis que seule la déclaration de structure sera visible pour l'en-tête public.
Compatibilité binaire
Masquer la définition de la structure peut également aider à fournir une marge de manœuvre pour préserver la compatibilité binaire dans les API Dylib. Il permet aux utilisateurs de la bibliothèque de modifier les champs de la structure opaque sans rompre la compatibilité binaire avec ceux qui utilisent la bibliothèque, car la nature de leur code a uniquement besoin de savoir ce qu’ils peuvent faire avec la structure, et non de la taille ou des champs. il a.
Par exemple, je peux réellement exécuter des programmes anciens construits sous Windows 95 (pas toujours parfaitement, mais étonnamment, beaucoup fonctionnent encore). Il est fort probable qu'une partie du code de ces anciens fichiers binaires utilisait des pointeurs opaques vers des structures dont la taille et le contenu avaient changé depuis Windows 95. Pourtant, les programmes continuent de fonctionner dans les nouvelles versions de Windows car ils n’ont pas été exposés au contenu de ces structures. Lorsque vous travaillez sur une bibliothèque où la compatibilité binaire est importante, les éléments auxquels le client n'est pas exposé sont généralement autorisés à changer sans que la compatibilité en amont ne soit supprimée.
Efficacité
Renvoyer une structure complète NULL serait plus difficile ou moins efficace, je suppose. Est-ce une raison valable?
Il est généralement moins efficace de supposer que le type peut correspondre à la pile et être alloué sur la pile, sauf si un allocateur de mémoire beaucoup moins généralisé est utilisé en coulisse malloc
, comme une mémoire de mise en pool allouée de taille fixe plutôt que de taille variable. Dans ce cas, c’est très probablement un compromis en matière de sécurité que de permettre aux développeurs de bibliothèques de conserver des invariants (garanties conceptuelles) liés à FILE
.
Ce n'est pas une raison valable, du moins du point de vue des performances, de fopen
renvoyer un pointeur, car la seule raison de son retour NULL
est l'échec de l'ouverture d'un fichier. Ce serait optimiser un scénario exceptionnel en contrepartie du ralentissement de tous les chemins d’exécution courants. Il peut y avoir une raison valable de productivité dans certains cas pour rendre les conceptions plus simples et leur permettre de renvoyer des pointeurs permettant NULL
de renvoyer des post-conditions.
Pour les opérations sur les fichiers, le temps système est relativement trivial par rapport aux opérations sur les fichiers eux-mêmes, et le manuel fclose
ne doit de toute façon pas être évité. Donc, ce n'est pas comme si nous pouvions éviter au client la tâche de libérer (fermer) la ressource en exposant la définition de FILE
et en la renvoyant par valeur fopen
ou en espérant une amélioration significative des performances compte tenu du coût relatif des opérations de fichier elles-mêmes afin d'éviter une allocation en tas .
Hotspots et correctifs
Dans d’autres cas, j’ai décrit beaucoup de code C inutile dans des bases de code héritées avec des points chauds malloc
et des ratés inutiles dans le cache en raison de l’utilisation trop fréquente de cette pratique avec des pointeurs opaques et de l’affectation inutile de trop nombreuses ressources inutilement, grandes boucles.
Une pratique alternative que j'utilise à la place consiste à exposer les définitions de structure, même si le client n'est pas censé les altérer, en utilisant un standard de convention de dénomination communiquant que personne d'autre ne devrait toucher aux champs:
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);
S'il y a des problèmes de compatibilité binaire à l'avenir, j'ai trouvé qu'il était suffisant de réserver de manière superflue de l'espace supplémentaire à des fins futures, comme ceci:
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;
};
Cet espace réservé est un peu inutile, mais peut vous sauver la vie si nous découvrons à l'avenir que nous devons ajouter des données supplémentaires Foo
sans casser les fichiers binaires qui utilisent notre bibliothèque.
À mon avis , le masquage d'informations et la compatibilité binaire sont généralement la seule raison convenable de n'autoriser que l'allocation de tas de structures en plus des structures de longueur variable (ce qui l'exigerait toujours, ou du moins serait un peu gênant si le client devait attribuer mémoire sur la pile en mode VLA pour allouer le VLS). Même les grandes structures sont souvent moins chères à retourner en valeur si cela signifie que le logiciel travaille beaucoup plus avec la mémoire chaude de la pile. Et même si cela ne coûtait pas moins cher de revenir en valeur sur la création, on pourrait simplement faire ceci:
int foo_create(struct Foo* foo);
...
/* In the client code: */
struct Foo foo;
if (foo_create(&foo))
{
foo_something(&foo);
foo_destroy(&foo);
}
... pour initialiser à Foo
partir de la pile sans possibilité de copie superflue. Ou le client a même la liberté d'allouer Foo
sur le tas s'il le souhaite pour une raison quelconque.