Quelle est la quantité de mémoire d'un objet dans la pile lors de l'utilisation du passage par référence?
Je pensais que l'utilisation du passage par référence ne ferait pas une copie de l'objet et qu'il n'y aurait pas la taille de l'objet sur la pile. Dans le code ci-dessous, il semble que la taille totale de l'objet soit poussée sur la pile, qu'il s'agisse d'une référence ou d'une valeur.

#include <iostream>
#include <stdint.h>

struct structWithSize_s{
  char data[1024*2];
};

void usePassPointer(structWithSize_s *t,uint64_t addr){
    int i;
    std::cout << "usePassPointer: stack: " << std::hex << &i 
              << std::dec << " size on stack: " << addr-uint64_t(&i) << std::endl;
    i = int(t->data[0]);
}
void usePassByValue(structWithSize_s t,uint64_t addr){
    int i;
    std::cout << "usePassByValue: stack: " << std::hex << &i 
              << std::dec << " size on stack: " << addr-uint64_t(&i) << std::endl;
    i = int(t.data[0]);
    usePassPointer(&t,uint64_t(&i));
}
void usePassByRef(structWithSize_s &t,uint64_t addr){
    int i;
    std::cout << "usePassByRef: stack: " << std::hex << &i 
              << std::dec << " size on stack: " << addr-uint64_t(&i) << std::endl;
    i = int(t.data[0]);
    usePassByValue(t,uint64_t(&i));
}

int main(void){
    int i;
    std::cout << "Base Stack: " << std::hex << &i << std::dec << std::endl;
    structWithSize_s t;
    std::cout << "Sizeof(t): " << sizeof(t) <<  std::endl;
    usePassByRef(t,uint64_t(&i));
    char d;
    std::cin >> d;
    return 0;
}

Le résultat:

$ g++ -std=c++0x -O0 -fno-implement-inlines -g t.cpp

$ ./a.out
Base Stack: 0x7ffff2826c8c
Sizeof(t): 2048
usePassByRef: stack: 0x7ffff282645c size on stack: 2096
usePassByValue: stack: 0x7ffff2825c1c size on stack: 2112
usePassPointer: stack: 0x7ffff2825bdc size on stack: 64
-1
histrung 20 avril 2017 à 18:46

3 réponses

Meilleure réponse

Votre méthode de test de la taille de la pile n'est ... pas fiable pour le dire légèrement.

Pourquoi ne pas aller directement à la gueule du cheval:

Préparons la scène:

struct X {
  char data[1024];
};

void by_value(X x);
void by_ref(X& x);
void by_ptr(X* x);

void test_by_value()
{
    X x;
    by_value(x);
}

void test_by_ref()
{
    X x;
    by_ref(x);
}

void test_by_ptr()
{
    X x;
    by_ptr(&x);
}

Et maintenant, voyons ce que nous avons réellement:

test_by_value():                     # @test_by_value()
        sub     rsp, 2056 // <-- stack increase
        lea     rsi, [rsp + 1032]
        mov     ecx, 128
        mov     rdi, rsp
        rep movsq
        call    by_value(X)
        add     rsp, 2056
        ret

test_by_ref():                       # @test_by_ref()
        sub     rsp, 1032  // <-- stack increase
        lea     rdi, [rsp + 8]
        call    by_ref(X&)
        add     rsp, 1032
        ret

test_by_ptr():                       # @test_by_ptr()
        sub     rsp, 1032  // <-- stack increase
        lea     rdi, [rsp + 8]
        call    by_ptr(X*)
        add     rsp, 1032
        ret

Comme vous pouvez le voir dans le test_by_value, la pile est augmentée de 2056, ce qui est à peu près la taille de la variable locale x (1024) + la taille du paramètre copié + autres manigances de pile.

Alors que dans test_by_ref et test_by_ptr la pile est augmentée de 1032 ce qui prouve que l'objet x n'est pas dupliqué sur la pile.

Voilà: la preuve empirique que le passage par référence n'utilise pas la pile pour tout l'objet.


Revenons à votre méthode de test. Au lycée et dans une certaine mesure à la faculté, on m'a montré "empiler des concepts" en faisant les choses de la même manière que vous: en observant les adresses de variables locales et en les comparant.

Cependant, j'ai une aversion pour cette méthode. Je ne parle même pas du problème de la portabilité. Mais à moins que vous ne compreniez très bien le contrat ABI et l'implémentation du compilateur et les implémentations d'optimisation (oui, le compilateur fait quelques optimisations même avec -O0), vous obtenez des résultats dont vous ne comprenez pas vraiment d'où ils viennent et vous vous précipitez vers donnez-leur des interprétations qui ne sont pas vraies.


Vous pouvez le voir en action ici: https://godbolt.org/g/8562LD

Clang tronc avec

-std=c++1z -Wall -Wextra -O3 -fno-implement-inlines -march=native

Gcc donne des résultats similaires

4
bolov 20 avril 2017 à 16:25

i est avant structWithSize_s t sur la pile. sizeof(structWithSize_s) est ce que vous mesurez, pas sizeof(structWithSize_s &).

Déplacez i ci-dessous structWithSize_s t.

0
Yakk - Adam Nevraumont 20 avril 2017 à 16:10

Je pensais que l'utilisation du passage par référence ne ferait pas une copie de l'objet

En effet, ce n'est pas le cas.

Combien de mémoire une référence utilise-t-elle sur la pile

Peut-être pas du tout si aucun stockage n'est nécessaire.

Cependant, à moins que la fonction ne soit étendue en ligne, un argument de référence doit avoir un stockage en pratique. Voici une astuce pour obtenir la taille qu'une référence occuperait en mémoire:

struct test {
    structWithSize_s &t;
};

size_t size = sizeof(test);

En pratique, ce sera probablement exactement la même chose qu'un pointeur.

2
eerorika 20 avril 2017 à 16:27