Pourquoi le code C ne renvoie-t-il pas une structure?

Bien que ce soit très pratique, je rencontre rarement, voire jamais, des fonctions qui renvoient des struct (ou des union ) en C, qu’il s’agisse de fonctions liées dynamicment ou de fonctions définies de manière statique.
Au lieu de cela, ils renvoient les données via un paramètre de pointeur.

(Un exemple dynamic sous Windows est GetSystemInfo .)

Quelle est la raison derrière cela?
Est-ce à cause d’un problème de performances, d’un problème de compatibilité ABI ou de quelque chose d’autre?

Je dirais “performance”, plus le fait qu’il soit même possible semble parfois surprendre les programmeurs C. Ce n’est pas … dans la “saveur” générale de C, pour beaucoup, de jeter de gros objects tels que des structs comme s’ils n’étaient que de simples valeurs. Ce que, selon la langue, ils sont vraiment.

Dans le même ordre d’idées, de nombreux programmeurs C semblent avoir automatiquement recours à memcpy() lorsque le besoin de copier des structures se fait sentir, plutôt que de simplement utiliser une affectation.

Du moins en C ++, il existe quelque chose appelé “optimisation de la valeur de retour” qui est capable de transformer en silence le code comme ceci:

 struct Point { int x, y; }; struct Point point_new(int x, int y) { struct Point p; px = x; py = y; return p; } 

dans:

 void point_new(struct Point *return_value, int x, int y) { struct Point p; px = x; py = y; *return_value = p; } 

qui supprime le retour “vrai” (potentiellement gourmand en stack) d’une valeur de struct. Je suppose que même mieux serait ceci, pas sûr si elles sont aussi intelligentes:

 void point_new(struct Point *return_value, int x, int y) { return_value->x = x; return_value->y = y; } 

Je ne sais pas si les compilateurs C peuvent faire quoi que ce soit. S’ils ne le peuvent pas, je suppose que cela pourrait constituer un véritable argument contre les retours de structure, pour des programmes très critiques en termes de performances.

Les raisons sont principalement historiques. Dans son article “The Text Editor sam ” , Rob Pike écrit:

Une question connexe de style de programmation: sam passe fréquemment les structures par valeur, ce qui simplifie le code. Traditionnellement, les programmes C passaient des structures par référence, mais l’allocation implicite sur la stack était plus facile à utiliser. Le transfert de structure est une caractéristique relativement nouvelle de C (il ne figure pas dans le manuel de référence standard pour C 14 ) et est mal pris en charge par la plupart des compilateurs C commerciaux. C’est pratique et expressif, cependant, et simplifie la gestion de la mémoire en évitant l’allocateur et en éliminant les alias de pointeur.

Cela étant dit, la technique présente des pièges; Si vous retournez des structures de grande taille, vous risquez un débordement

Les retours en C sont effectués en stockant la valeur de retour dans la stack .
Retourner une structure ou une union entraînerait la mise sur la stack de données potentiellement très volumineuses, ce qui pourrait entraîner un débordement de la stack .

Renvoyer uniquement un pointeur à la structure / union est plus sûr, car vous ne mettez qu’une petite quantité de données (4 octets en général) dans la stack.

La fonction AC peut renvoyer une structure (tout comme une structure C ++, où elle est assez courante). Peut-être que dans les toutes premières années de C, il ne pourrait pas.

La spécification ABI x86-64 pour Linux et les systèmes associés (page 21) indique même que les structures pouvant contenir deux mots de -64 bits peuvent souvent être renvoyées dans deux registres sans passer par la mémoire (même la stack). Très probablement, cela est plus rapide que d’aller dans la stack.

Comme l’indique la réponse dans sa réponse , l’ABI exige souvent que les résultats de la structure soient convertis en un pointeur invisible.

On pourrait même définir une autre convention d’appel renvoyant plus de données dans plus de registres. Mais ces nouvelles conventions casseraient tout le code object et nécessiteraient une recompilation de tout (y compris, même, sous Linux, des bibliothèques système telles que libc.so.6 ), et bien sûr, nécessiteraient de changer le compilateur.

Bien entendu, les conventions ABI sont liées au processeur, au système et au compilateur.

Je ne connais pas Windows et je ne sais pas ce que Windows définit comme son ABI.

Dans les versions antérieures à ANSI C, vous ne pouviez pas retourner d’object de type structure ni transmettre d’arguments de types de structure.

De Chris Torek cite dans comp.lang.c:

Notez que la version 6 C ne prend pas non plus en charge les arguments à valeur de structure et les valeurs de retour à valeur de structure.

La raison pour laquelle ils ne sont pas très utilisés de nos jours est que les gens préfèrent retourner un pointeur à une structure qui implique uniquement une copie du pointeur au lieu de la copie d’un object de structure entier.

En plus de l’idée qu’il pourrait y avoir un impact négatif sur les performances ou que les structures retournées par valeur n’auraient peut-être pas été généralement sockets en charge au cours des jours antérieurs vous ne pouvez pas facilement retourner un indicateur de réussite / échec. Je sais que je commençais occasionnellement à concevoir une fonction pour renvoyer une structure initialisée dans la fonction, mais je me demandais ensuite comment indiquer si la fonction avait réussi ou non. Vous avez à peu près les options suivantes:

  • garantir le succès (parfois c’est possible)
  • passer un pointeur à un emplacement pour le code d’erreur
  • avoir un champ ou une valeur sentinelle dans la structure qui indique le succès / l’échec

Seule l’option 1 empêche l’interface d’être un kludge. La deuxième option empêche de renvoyer la structure par valeur et rend la fonction plus difficile à utiliser pour gérer les échecs. La troisième option n’est tout simplement pas une bonne conception dans presque tous les cas.

En général, les fonctions Windows ne renvoient rien ou des codes d’erreur, notamment lorsqu’il s’agit de renvoyer des structures ou des classes.

L’efficacité pourrait être un problème, bien que RVO devrait éliminer les frais généraux.

Je pense que la raison principale était de garder les méthodes en ligne avec le style de codage utilisé auparavant.