Comment la fonction retourne-t-elle ACTUELLEMENT la variable struct dans C?

Comment une valeur de retour de fonction est claire pour moi, juste pour démarrer:

int f() { int a = 2; return a; } 

Maintenant, la mémoire est empilée et sa durée de vie se situe dans le f() afin de renvoyer la valeur copiée dans un registre spécial qui est lu par l’appelant car il sait que l’appelé a placé la valeur pour lui. . (Étant donné que la taille de la taille de registre spéciale du porte-valeur de retour est limitée, nous ne pouvons pas renvoyer d’objects de grande taille.

Revenons à C pour une situation où je veux retourner une variable struct pas un pointeur:

 struct inventory { char name[20]; int number; }; struct inventory function(); int main() { struct inventory items; items=function(); printf("\nam in main\n"); printf("\n%s\t",items.name); printf(" %d\t",items.number); getch(); return 0; } struct inventory function() { struct inventory items; printf(" enter the item name\n "); scanf(" %s ",&items.name ); printf(" enter the number of items\n "); scanf("%d",&items.number ); return items; } 

Code issu de: https://stackoverflow.com/a/22952975/962545

Voici le deal,

Commençons par main, la variable items déclarée mais non initialisée, puis la fonction est appelée qui renvoie la variable de structure initialisée qui est copiée dans celle principale. Maintenant, je suis un peu flou de comprendre comment function() renvoyé des items variable struct qui ne sont pas créés dynamicment (techniquement pas en tas) donc la durée de vie de cette variable se situe dans le corps de la function() pour tenir dans un registre spécial, alors pourquoi ça a fonctionné? (Je sais que nous pouvons allouer dynamicment un élément à l’intérieur d’une fonction et renvoyer l’adresse mais je ne veux pas d’alternative, je cherche des explications)

Question: Bien que cela fonctionne, mais comment function() retourne-t-elle la variable struct et est-elle copiée dans la variable items dans main quand elle est supposée mourir avec function() return.

Je manque sûrement une chose importante, une explication détaillée serait utile. 🙂

EDIT: Autre réponse Références:

  1. https://stackoverflow.com/a/2155742/962545
  2. Nommé comme optimisation de la valeur de retour

Les détails varient considérablement selon la convention d’appel. Certains ABI n’ont pas de convention d’appel pour le passage de structures entières, auquel cas le compilateur est libre de faire ce qu’il juge utile.

Les exemples comprennent:

  • Passer et renvoyer la structure entière sous la forme d’une série de registres consécutifs (souvent utilisés avec de “petites” structures)
  • Placer la structure entière en tant que bloc d’argument sur la stack
  • Allouer un argument vide assez grand pour contenir la structure, à remplir avec une valeur de retour
  • Passer l’adresse (stack) de la structure en argument (comme si la fonction avait été déclarée void function(struct inventory *) )

Chacune de ces implémentations pourrait être conforme à la spécification C ici. Mais, regardons une implémentation spécifique: la sortie de mon compilateur croisé GCC ARM.

La compilation du code que vous avez donné me donne ceci:

 main: stmfd sp!, {fp, lr} add fp, sp, #4 sub sp, sp, #48 sub r3, fp, #52 mov r0, r3 bl function(PLT) 

Les opérandes de destination sont toujours à gauche. Vous pouvez voir que le programme réserve de l’espace de stack, puis transmet l’adresse de l’espace de stack comme r0 (le premier argument de la convention d’appel ARM EABI). function ne prend aucun argument, donc cet argument est clairement un argument artificiel ajouté par notre compilateur.

function ressemble à ceci:

 function: stmfd sp!, {r4, fp, lr} add fp, sp, #8 sub sp, sp, #36 str r0, [fp, #-40] ldr r3, .L6 ... add r2, pc, r2 mov r0, r2 mov r1, r3 bl scanf(PLT) ldr r3, [fp, #-40] mov ip, r3 sub r4, fp, #36 ldmia r4!, {r0, r1, r2, r3} stmia ip!, {r0, r1, r2, r3} ldmia r4, {r0, r1} stmia ip, {r0, r1} ldr r0, [fp, #-40] sub sp, fp, #8 ldmfd sp!, {r4, fp, pc} 

Ce code cache essentiellement l’argument unique dans [fp, #-40] , puis le charge plus tard et commence à cacher les données à l’adresse indiquée. À la fin, il renvoie à nouveau cette valeur de pointeur dans r0 . Effectivement, le compilateur a fait la signature de la fonction dans

 struct inventory *function(struct inventory *) 

où la structure renvoyée est allouée sur la stack par l’appelant, transmise et renvoyée.

Vous manquez la chose la plus évidente: la manière de passer / de renvoyer C: tout est passé arounbd par valeur , ou du moins: il se comporte de cette façon.

C’est-à-dire:

 struct foo some_f( void ) { struct foo local = { .member = 123, .bar = 2.0 }; //some awsome code return local; } 

Travaillera, très bien. Si la structure est petite, il est possible que ce code crée une variable de structure locale et renvoie une copie de cette structure à l’appelant.
Dans d’autres cas, cependant, ce code se traduira approximativement par:

 void caller() { struct foo hidden_stack_space; struct foo your_var = *(some_f(&hidden_stack_space)); } //and the some_f function will behave as: struct foo * some_f(struct foo * local) { //works on local and return local; } 

Eh bien, ce n’est pas exactement ce qui se passe tout le temps , mais cela se résume à cela, plus ou moins. Le résultat sera le même, mais les compilateurs peuvent se comporter différemment dans ce cas.

La ligne du bas est la suivante: C renvoie par valeur, donc votre code fonctionne correctement. Cependant, il y a des pièges:

 struct foo { int member1; char *str; }; struct foo some_f() { char bar[] = "foobar"; struct foo local = { .member1 = 123, .str = &bar[0] }; return local; } 

Est dangereux: le pointeur assigné à local.str pointe vers la mémoire qui sera libérée une fois la structure renvoyée. Dans ce cas, les problèmes que vous attendiez avec ce code sont vrais: cette mémoire n’est plus (ou n’est plus valide).
Tout simplement parce qu’un pointeur est une variable dont la valeur est l’adresse mem et que cette valeur est renvoyée / affectée.

Une structure, au moins une grande, sera allouée et retournée sur la stack et sera extraite de la stack (le cas échéant) par l’appelant. Le compilateur essaiera de l’allouer au même endroit où l’appelant s’attend à le trouver, mais il effectuera une copie si cela n’est pas possible. Il est possible, mais pas nécessaire, qu’il y ait également un pointeur sur la structure, renvoyé via des registres.

Bien sûr, les détails varient en fonction de l’architecture.