54 votes

En C, comment choisirais-je de renvoyer une structure ou un pointeur sur une structure?

Mon travail sur mon C muscle dernièrement et à la recherche par le biais de nombreuses bibliothèques, j'ai travaillé avec ses certainement m'a donné une bonne idée de ce qu'est une bonne pratique. Une chose que je n'ai PAS vu, c'est une fonction qui renvoie une structure:

something_t make_something() { ... }

De ce que j'ai absorbé, c'est la "bonne" façon de faire:

something_t *make_something() { ... }
void destroy_something(something_t *object) { ... }

L'architecture dans l'extrait de code 2 est BEAUCOUP plus populaire que l'extrait de code 1. Donc, maintenant, je le demande, pourquoi aurais-je retourner un struct directement, comme dans l'extrait de code 1? Quelles sont les différences à prendre en compte quand je suis à choisir entre les deux options?

En outre, comment fonctionne cette option comparer?

void make_something(something_t *object)

59voto

Jon Purdy Points 19408

Quand something_t est petit (lire: la copie est à peu près aussi bon marché que la copie d'un pointeur) et vous voulez qu'il soit attribué à la pile par défaut:

 something_t make_something(void);

something_t stack_thing = make_something();

something_t *heap_thing = malloc(sizeof *heap_thing);
*heap_thing = make_something();
 

Lorsque something_t est volumineux ou si vous souhaitez qu'il soit alloué en tas:

 something_t *make_something(void);

something_t *heap_thing = make_something();
 

Indépendamment de la taille de something_t , et si vous ne vous souciez pas de l'affectation:

 void make_something(something_t *);

something_t stack_thing;
make_something(&stack_thing);

something_t *heap_thing = malloc(sizeof *heap_thing);
make_something(heap_thing);
 

36voto

Yakk Points 31636

C'est presque toujours à propos de ABI de stabilité. Binaire de la stabilité entre les versions de la bibliothèque. Dans le cas où il ne l'est pas, c'est parfois avoir dynamiquement la taille des structures. Rarement, il est très grand structs ou de performance.


Il est extrêmement rare que l'attribution d'un struct sur le tas et de retour, il est presque aussi rapide que nous le retourner par valeur. L' struct devrait être énorme.

Vraiment, la vitesse n'est pas la raison derrière la technique 2, le retour-en-aiguille, au lieu de revenir par valeur.

Technique 2 existe pour ABI stabilité. Si vous avez un struct et de la prochaine version de la bibliothèque ajoute un autre 20 champs, les consommateurs de votre précédente version de la bibliothèque sont binaires compatibles s'ils sont remis pré-construit des pointeurs. Les données supplémentaires au-delà de la fin de l' struct qu'ils savent à propos de quelque chose qu'ils n'ont pas à connaître.

Si vous retournez sur la pile, l'appelant est de l'allocation de la mémoire pour elle, et ils doivent être d'accord avec vous sur la façon dont il est grand. Si votre bibliothèque mis à jour depuis leur dernière reconstruit, vous allez à la corbeille de la pile.

Technique 2 vous permet également de masquer les données supplémentaires, à la fois avant et après le pointeur de votre retour (les versions ajouter des données à la fin de la struct est une variante de). Vous pourriez fin de l'ouvrage, avec un tableau de taille variable, ou ajouter le pointeur avec quelques données supplémentaires, ou les deux.

Si vous voulez pile-alloués structs dans un ABI stable, presque toutes les fonctions que parler à l' struct d'avoir à passer des informations de version.

Donc

something_t make_something(unsigned library_version) { ... }

library_version est utilisé par la bibliothèque afin de déterminer quelle est la version de something_t il est prévu de revenir et il change la façon dont beaucoup de la pile, il manipule. Ce n'est pas possible en utilisant la norme C, mais

void make_something(something_t* here) { ... }

est. Dans ce cas, something_t pourrait avoir un version champ de son premier élément (ou d'une taille de champ), et vous auriez besoin d'être renseigné avant de faire appel à make_something.

D'autres bibliothèques de code en prenant un something_t serait alors interroger l' version champ pour déterminer quelle version de something_t qu'ils travaillent avec.

13voto

Lundin Points 21616

En règle générale, vous ne devriez jamais passer struct des objets par valeur. Dans la pratique, il sera bien de le faire aussi longtemps qu'ils sont de plus petite ou égale à la taille maximale que votre PROCESSEUR peut traiter en une seule instruction. Mais du point de vue stylistique, on évite généralement ce même alors. Si vous n'avez jamais passer par les structures de la valeur que vous pouvez ensuite sur ajouter des membres à la structure et il n'affecte pas le rendement.

Je pense qu' void make_something(something_t *object) est la façon la plus courante d'utiliser des structures en C. Vous quittez la répartition de l'appelant. Il est efficace mais pas assez.

Cependant, orienté objet C utilisation de programmes something_t *make_something() depuis qu'ils sont intégrés avec le concept de l' opaque, ce qui vous oblige à utiliser des pointeurs. Si le pointeur retourné points à la mémoire dynamique ou autre chose dépend de la mise en œuvre. OO opaque type est souvent l'un des plus élégants et les meilleures façons de conception plus complexe de programmes en C, mais malheureusement, peu de programmeurs C ou les soins à ce sujet.

9voto

Matt McNabb Points 14273

Certains pros de la première approche:

  • Moins de code à écrire.
  • Plus idiomatiques pour le cas d'utilisation de retourner plusieurs valeurs.
  • Travaille sur des systèmes qui n'ont pas d'allocation dynamique.
  • Probablement plus rapide pour les petites ou de petits objets.
  • Pas de fuite de mémoire dû à oublier d' free.

Certains inconvénients:

  • Si l'objet est volumineux (par exemple, un mégaoctet) , peut provoquer un débordement de pile, ou peut-être lent si les compilateurs ne pas l'optimiser.
  • Peut surprendre les gens qui ont appris C dans les années 1970, quand ce n'était pas possible, et ils n'ont pas su jusqu'à ce jour.
  • Ne fonctionne pas avec les objets qui contiennent un pointeur vers une partie de lui-même.

4voto

Malcolm McLean Points 5437

Je suis un peu surpris.

La différence est que l'exemple 1 crée une structure sur la pile, exemple 2 le crée sur le tas. En C, C++ ou code qui est effectivement C, c'est idiomatiques et pratique pour créer la plupart des objets sur le tas. En C++ il n'est pas, pour la plupart, ils vont sur la pile. La raison en est que si vous créez un objet sur la pile, le destructeur est appelé automatiquement, si vous créez sur le tas, il doit être appelé explicitement.Il est donc beaucoup plus facile de s'assurer il n'y a pas de fuites de mémoire et à la gestion des exceptions est à tout va sur la pile. En C, le destructeur doit être appelé explicitement de toute façon, et il n'y a pas de concept de spécial fonction destructeur (vous avez destructeurs, bien sûr, mais ils sont juste des fonctions avec des noms comme destroy_myobject()).

Maintenant, l'exception en C++ est de faible niveau de conteneur d'objets, p.ex. des vecteurs, des arbres, des cartes de hachage et ainsi de suite. Ces retiennent les tas de membres, et ils sont destructeurs. Aujourd'hui, la plupart de la mémoire des objets lourds se composent de quelques données immédiates de membres donnant tailles, ids, étiquettes et ainsi de suite, et puis le reste de l'information dans la STL structures, peut-être un vecteur de données de pixels ou d'une carte mot anglais / paires de valeurs. Donc, la plupart des données est en fait sur le tas, même en C++.

Et le C++ moderne est conçu de sorte que ce modèle

class big
{
    std::vector<double> observations; // thousands of observations
    int station_x;                    // a bit of data associated with them
    int station_y; 
    std::string station_name; 
}  

big retrieveobservations(int a, int b, int c)
{
    big answer;
    //  lots of code to fill in the structure here

    return answer;
}

void high_level()
{
   big myobservations = retriveobservations(1, 2, 3);
}

Compiler assez de code efficace. La grande observation membre ne génère pas inutile makework copies.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X