С++ Возврат из функции по значению

112
28 мая 2021, 17:00

Я новичок в С++ 11, поэтому возникают подобные вопросы))

Собственно, имеем такой код:

struct testStruct
{
    int x = 10;
    int y = 11;
    int z = 12;
};
testStruct foo() 
{
    return testStruct();
}
int main(int _argc, char* _argv[])
{
    testStruct&& rv_ref = foo();
    rv_ref.z = 254;
    return 0;
}

Из которого мы при дизассемблировании получаем такой машинный код:

int main(int _argc, char* _argv[])
{
00007FF6996114F0  mov         qword ptr [rsp+10h],rdx  
00007FF6996114F5  mov         dword ptr [rsp+8],ecx  
00007FF6996114F9  push        rsi  
00007FF6996114FA  push        rdi  
00007FF6996114FB  sub         rsp,78h  
00007FF6996114FF  mov         rdi,rsp  
00007FF699611502  mov         ecx,1Eh  
00007FF699611507  mov         eax,0CCCCCCCCh  
00007FF69961150C  rep stos    dword ptr [rdi]  
00007FF69961150E  mov         ecx,dword ptr [_argc]  
    testStruct&& rv_ref = foo();
00007FF699611515  lea         rcx,[rsp+60h]  
00007FF69961151A  call        foo (07FF69961124Eh)  
00007FF69961151F  lea         rcx,[rsp+54h]  
00007FF699611524  mov         rdi,rcx  
00007FF699611527  mov         rsi,rax  
00007FF69961152A  mov         ecx,0Ch  
00007FF69961152F  rep movs    byte ptr [rdi],byte ptr [rsi]  
00007FF699611531  lea         rax,[rsp+38h]  
00007FF699611536  lea         rcx,[rsp+54h]  
00007FF69961153B  mov         rdi,rax  
00007FF69961153E  mov         rsi,rcx  
00007FF699611541  mov         ecx,0Ch  
00007FF699611546  rep movs    byte ptr [rdi],byte ptr [rsi]  
00007FF699611548  lea         rax,[rsp+38h]  
00007FF69961154D  mov         qword ptr [rv_ref],rax  
    rv_ref.z = 254;
00007FF699611552  mov         rax,qword ptr [rv_ref]  
00007FF699611557  mov         dword ptr [rax+8],0FEh
return 0;
00007FF69961155E  xor         eax,eax  
}

Обратите внимание на строки

00007FF69961152F  rep movs    byte ptr [rdi],byte ptr [rsi] 

и

00007FF699611546  rep movs    byte ptr [rdi],byte ptr [rsi] 

Они производят копирование данных из одного куска памяти на стеке, в другой кусок памяти на стеке.

Так вот, зачем возвращаемое из функции foo() значение копируется на стек вызывающей функции main() ЦЕЛЫХ 2 РАЗА???

По логике, хватило бы даже одного того участка стекового кадра, который использовался для возврата значения из функции foo(). Однако же в сгенерированном машинном коде, мы видим целых две копии возвращаемого значения!

Что интересно, если я даже уберу из кода ссылку на r-value, вот так:

int main(int _argc, char* _argv[])
{
    foo();
    return 0;
} 

То в сгенерированном машинном коде все равно останется копирование возвращаемого значения на стек вызывающей функции!

int main(int _argc, char* _argv[])
{
00007FF6747F14D0  mov         qword ptr [rsp+10h],rdx  
00007FF6747F14D5  mov         dword ptr [rsp+8],ecx  
00007FF6747F14D9  push        rsi  
00007FF6747F14DA  push        rdi  
00007FF6747F14DB  sub         rsp,48h  
00007FF6747F14DF  mov         rdi,rsp  
00007FF6747F14E2  mov         ecx,12h  
00007FF6747F14E7  mov         eax,0CCCCCCCCh  
00007FF6747F14EC  rep stos    dword ptr [rdi]  
00007FF6747F14EE  mov         ecx,dword ptr [_argc]  
    foo(127);
00007FF6747F14F2  mov         edx,7Fh  
00007FF6747F14F7  lea         rcx,[rsp+30h]  
00007FF6747F14FC  call        foo (07FF6747F114Fh)  
00007FF6747F1501  lea         rcx,[rsp+20h]  
00007FF6747F1506  mov         rdi,rcx  
00007FF6747F1509  mov         rsi,rax  
00007FF6747F150C  mov         ecx,0Ch  
00007FF6747F1511  rep movs    byte ptr [rdi],byte ptr [rsi]
return 0;
00007FF6747F1513  xor         eax,eax  
}

При чем в некоторых случаях сохраняется двойное копирование, как в предыдущем листинге (в этот раз я просто не поймал этот дизассемблер, от чего зависит количество копирований возвращаемого значения, не знаю)

Так вот, зачем делать столько копий возвращаемого значения на стеке вызывающей функции???

Является ли это всего лишь следствием того, что современные компиляторы, как и многие другие программные продукты являются дикой, многоуровневой матрешкой и разработчики, напрмер, некоего парсера возвращаемых значений функции (не ругайте меня, нуба, если что, за подобные выражения)) совсем не в курсах о том как происходит генерация машинного кода, потому что для них это уже 100500-я абстракция. Или же это имеет некий пока непонятный для меня, сакральный смысл, реализованный в колдунстве со всеми этими r-value, x-value, const и прочей мишурой нового с++ 11, обеспечивающий, например, некие неизменяемые копии, мувы и т.д?!

Буду очень благодарен за пояснения!

Answer 1

А это ничего, что VC++17 при компиляции дает

main    PROC                        ; COMDAT
; 22   :     testStruct&& rv_ref = foo();
; 23   :     rv_ref.z = 254;
; 24   :     return 0;
    xor eax, eax
; 25   : }
    ret 0
main    ENDP

? :)
Выходной ассемблерный код может быть... ну, несколько различным, так что...

READ ALSO
Использование свойств в С++

Использование свойств в С++

В С++ для атрибутов класса можно задавать свойства, шаблон определения которых -

92
Как обратиться к другому слоту Qt

Как обратиться к другому слоту Qt

Пытаюсь в конструкторе B вызвать слот A::toExit()Как исправить ошибку?

86
С помощью чего лучше создать графический вывод бинарного файла(C++, QT)?

С помощью чего лучше создать графический вывод бинарного файла(C++, QT)?

Изначально есть бинарный файлМне полностью известна его структура

185