187 votes

Y a-t-il des inconvénients à structs en passant par la valeur de C, plutôt que de passer un pointeur ?

Existe-il des inconvénients à passer par les structures de la valeur C, plutôt que de passer un pointeur?

Si la structure est grande, il est évidemment le performancd aspect de la copie de beaucoup de données, mais pour une petite structure, il devrait être le même que le passage de plusieurs valeurs pour une fonction.

C'est peut-être encore plus intéressant lorsqu'il est utilisé comme valeur de retour. C ne dispose que seules les valeurs de retour des fonctions, mais vous avez souvent besoin de plusieurs années. Ainsi, une solution simple est de les mettre dans une struct et de retour que.

Existe-il des raisons pour ou contre?

Car elle pourrait ne pas être évident pour tout le monde ce dont je parle ici, je vais donner un exemple simple.

Si vous êtes à la programmation en C, vous aurez tôt ou tard commencer à écrire des fonctions qui ressemblent à ceci:

void examine_data(const char *ptr, size_t len)
{
    ...
}

char *p = ...;
size_t l = ...;
examine_data(p, l);

Ce n'est pas un problème. Le seul problème est que vous devez être d'accord avec votre collègue de travail, dans lequel la commande, les paramètres doivent être de sorte que vous utiliser la même convention, dans toutes les fonctions.

Mais qu'advient-il lorsque vous voulez retourner le même genre d'information? Généralement, vous obtenez quelque chose comme ceci:

char *get_data(size_t *len);
{
    ...
    *len = ...datalen...;
    return ...data...;
}
size_t len;
char *p = get_data(&len);

Cela fonctionne bien, mais est beaucoup plus problématique. Une valeur de retour est une valeur de retour, sauf que dans cette mise en œuvre, il n'est pas. Il n'y a aucun moyen de dire à partir de ce qui précède que la fonction get_data n'est pas permis de regarder ce que len points. Et il n'y a rien qui fait que le compilateur de vérifier qu'une valeur est retournée par le pointeur. Donc, le mois prochain, quand quelqu'un modifie le code sans le comprendre correctement (parce qu'il n'a pas lu la documentation?) il est cassé sans que personne s'en aperçoive, ou il commence à s'écraser au hasard.

Donc, la solution que je propose est simple struct

struct blob { char *ptr; size_t len; }

Les exemples peuvent être réécrites comme ceci:

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();

Pour une raison quelconque, je pense que la plupart des gens instinctivement faire examine_data prendre un pointeur vers une struct blob, mais je ne vois pas pourquoi. Il obtient toujours un pointeur et un entier, il est juste beaucoup plus clair qu'ils vont de pair. Et dans le get_data cas, il est impossible de désordre dans la façon dont je l'ai décrit avant, car il n'existe pas de valeur d'entrée pour la longueur, et il doit y avoir un retour de la longueur.

235voto

Roddy Points 32503

Pour les petites structures (par exemple point, rect) passage par valeur est parfaitement acceptable. Mais, en dehors de vitesse, il y a une autre raison pourquoi vous devez être prudent en passant/le retour des grandes structures en valeur: l'espace de Pile.

Beaucoup de la programmation en C est pour les systèmes embarqués, où la mémoire est à une prime, et les tailles de tapis peut être mesurée en KO ou même d'Octets... Si vous êtes de passage ou de retour des structs, en valeur, des copies de ces structures seront placés sur la pile, ce qui peut causer de la situation que ce site est nommé d'après...

Si je vois une application qui semble avoir excessive de l'utilisation des piles, les structures passé par valeur est l'une des choses que je recherche en premier.

70voto

tonylo Points 2058

L'une des raisons de ne pas faire ce qui n'a pas été mentionné, c'est que cela peut causer un problème où la compatibilité binaire des questions.

Selon le compilateur utilisé, les structures peuvent être transmises par le biais de la pile ou de registres en fonction des options du compilateur/mise en œuvre

Voir: http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html

-cpac-struct-retour

-freg-struct-retour

Si deux compilateurs sont en désaccord, les choses peuvent exploser. Inutile de dire que les principales raisons de ne pas le faire sont illustrés sont pile de la consommation et des raisons de performances.

23voto

kizzx2 Points 6919

Pour vraiment répondre à cette question, on doit creuser profondément dans l'assemblée des terres:

(L'exemple suivant utilise gcc sur x86_64. Toute personne est la bienvenue afin d'ajouter d'autres architectures, telles que MSVC, BRAS, etc.)

Nous allons avoir notre exemple de programme:

// foo.c

typedef struct
{
    double x, y;
} point;

void give_two_doubles(double * x, double * y)
{
    *x = 1.0;
    *y = 2.0;
}

point give_point()
{
    point a = {1.0, 2.0};
    return a;
}

int main()
{
    return 0;
}

Compiler avec plein d'optimisations

gcc -Wall -O3 foo.c -o foo

L'assemblée:

objdump -d foo | vim -

C'est ce que nous obtenons:

0000000000400480 <give_two_doubles>:
    400480: 48 ba 00 00 00 00 00    mov    $0x3ff0000000000000,%rdx
    400487: 00 f0 3f 
    40048a: 48 b8 00 00 00 00 00    mov    $0x4000000000000000,%rax
    400491: 00 00 40 
    400494: 48 89 17                mov    %rdx,(%rdi)
    400497: 48 89 06                mov    %rax,(%rsi)
    40049a: c3                      retq   
    40049b: 0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000004004a0 <give_point>:
    4004a0: 66 0f 28 05 28 01 00    movapd 0x128(%rip),%xmm0
    4004a7: 00 
    4004a8: 66 0f 29 44 24 e8       movapd %xmm0,-0x18(%rsp)
    4004ae: f2 0f 10 05 12 01 00    movsd  0x112(%rip),%xmm0
    4004b5: 00 
    4004b6: f2 0f 10 4c 24 f0       movsd  -0x10(%rsp),%xmm1
    4004bc: c3                      retq   
    4004bd: 0f 1f 00                nopl   (%rax)

À l'exclusion de l' nopl plaquettes, give_two_doubles() a 27 octets alors qu' give_point() a 29 octets. D'autre part, give_point() rendements, moins une instruction de give_two_doubles()

Ce qui est intéressant, c'est que nous remarquons le compilateur a été en mesure d'optimiser mov dans le plus rapide SSE2 variantes movapd et movsd. En outre, give_two_doubles() fait de déplacer des données dans et hors de la mémoire, ce qui rend les choses lentement.

Apparemment, une grande partie de cela peut ne pas être applicables dans des environnements embarqués (qui est l'endroit où le terrain de jeu pour C est la plupart du temps aujourd'hui). Je ne suis pas une assemblée de l'assistant afin de tout commentaire serait le bienvenu!

15voto

Ilya Points 2279

La solution la plus Simple sera de retour un code d'erreur en tant que valeur de retour et tout le reste comme un paramètre de la fonction,
Ce paramètre peut être un struct, bien sûr, mais ne vois pas d'avantage particulier en passant par cette valeur, vient d'envoyer un pointeur.
En passant de la structure par valeur est dangereux, vous devez être très prudent de ce que vous êtes de passage sont, rappelez-vous il n'y a pas de constructeur de copie en C, si l'un des paramètres de structure est un pointeur le pointeur de la valeur sera copié il peut être très déroutant et difficile à maintenir.

Juste pour compléter la réponse (crédit complet à Roddy ) la pile d'utilisation est une autre raison de ne pas passer de la structure par valeur, croyez-moi, le débogage de débordement de pile est vrai pain PITA.

Replay de commentaire:

En passant struct par pointeur sens qu'une certaine entité a un droit de propriété sur cet objet et avoir une pleine connaissance de ce qui et quand doit être libéré. En passant struct en valeur, créer des références cachées à l'intérieur des données de structure (les pointeurs à l'autre, les structures, etc .. ), cela est difficile à maintenir (possible, mais pourquoi ?) .

9voto

Greg Hewgill Points 356191

Je dirais en passant (pas trop grand) des structures en valeur, à la fois en tant que paramètres et valeurs de retour, est parfaitement légitime technique. On doit prendre soin, bien sûr, que la structure est un type POD, ou la sémantique de copie sont bien spécifiés.

Mise à jour: Désolé, j'ai eu mon C++ chapeau de pensée sur. Je me souviens d'un temps où il n'était pas légal en C pour revenir à une structure à partir d'une fonction, mais cela a probablement changé depuis. Je voudrais encore dire que c'est valable aussi longtemps que tous les compilateurs vous prévoyez d'utiliser le support de la pratique.

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