web-dev-qa-db-fra.com

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

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

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

Maintenant, a obtient la mémoire en pile et sa durée de vie est dans la f() afin de retourner la valeur, elle copie la valeur dans un registre spécial qui est lu par l'appelant comme il le sait que l'appelé a placé la valeur pour lui. (Étant donné que la taille du registre spécial de détenteur de valeur de retour est limitée, c'est pourquoi nous ne pouvons pas retourner de gros objets, donc en cas de langues avancées lorsque nous voulons renvoyer la fonction d'objet copie en fait l'adresse de l'objet en tas dans ce registre spécial)

Revenons à C pour une situation où je veux retourner une variable struct et non 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 l'affaire,

Commençons par la variable principale, 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 de la variable main. Maintenant, je suis un peu floue pour comprendre comment function() a retourné la variable struct items qui n'est pas créée dynamiquement (techniquement pas dans le tas) donc la durée de vie de cette variable est dans la function() corps, également la taille de la variable item peut être assez énorme pour ne pas tenir dans un registre spécial, alors pourquoi cela a fonctionné?. (Je sais que nous pouvons allouer dynamiquement l'élément à l'intérieur de la fonction et renvoyer l'adresse mais je ne veux pas d'alternative , Je cherche des explications)

Question: Bien que cela fonctionne mais comment fonctionne function() a retourné la variable struct et a été copié dans la variable items en principal quand il est supposé mourir avec function() return.

Je manque sûrement une chose importante, une explication détaillée aiderait. :)

EDIT: Autres références de réponse:

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

Les détails varient considérablement en appelant la convention. Certains ABI n'ont pas de convention d'appel pour passer des structures entières, auquel cas le compilateur est libre de faire tout ce qui lui semble logique.

Les exemples comprennent:

  • Passer et renvoyer la structure entière comme une série de registres consécutifs (souvent utilisés avec de "petites" structures)
  • Placer la structure entière comme un bloc d'arguments sur la pile
  • Allouer un argument vide assez grand pour contenir la structure, à remplir avec une valeur de retour
  • Passer l'adresse (pile) de la structure comme argument (comme si la fonction était 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 GCC ARM cross-compiler.

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 l'espace de pile, puis transmet l'adresse de l'espace de pile 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 le seul argument dans [fp, #-40], puis le charge plus tard et commence à stocker les données à l'adresse vers laquelle il pointe. À la fin, il renvoie cette valeur de pointeur dans r0 encore. En effet, le compilateur a transformé la signature de fonction en

struct inventory *function(struct inventory *)

où la structure renvoyée est allouée sur la pile par l'appelant, transmise, puis renvoyée.

34
nneonneo

Vous manquez la chose la plus évidente de la façon de C de passer/retourner des choses: tout est passé 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;
}

Fonctionnera, très bien. Si la structure est petite, il est possible que ce code crée une variable de structure locale et renvoie un 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.

En bout de ligne, c'est: C renvoie par valeur, donc votre code fonctionne bien. 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 affecté à local.str indique que la mémoire sera libérée une fois la structure retourné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 qui est valeur est l'adresse mem, et cette valeur est retournée/affectée.

10

Un struct, au moins un grand, sera alloué et retourné sur la pile, et sera sauté hors de la pile (le cas échéant) par l'appelant. Le compilateur essaiera de l'allouer au même endroit où l'appelant s'attend à trouver cela, mais il fera une copie si cela n'est pas possible. Il est possible, mais pas nécessaire, qu'il y ait aussi un pointeur vers la structure, renvoyé via les registres.

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

5
Adrian Ratnapala