C++從匯編的視角審視對(duì)象的創(chuàng)建問(wèn)題
前言
很久以前閱讀了 CSAPP 這本書,可惜看過(guò)的東西基本都忘記了,只知道一些工具可以幫助我分析。今天突然對(duì) “返回對(duì)象的函數(shù)” 很感興趣,于是分析了一下匯編。
返回對(duì)象的函數(shù)如下,現(xiàn)在有一種叫做 NRV 優(yōu)化的技術(shù)可以避免低效率,如果把它關(guān)掉,它將會(huì)執(zhí)行以下過(guò)程:創(chuàng)建一個(gè)對(duì)象 apple,然后返回一個(gè) apple,發(fā)生一次拷貝構(gòu)造函數(shù),所以存在執(zhí)行效率問(wèn)題;如果你還寫了 Apple a = GetApple();
,那么還將會(huì)發(fā)生一次賦值拷貝構(gòu)造。
Apple GetApple() { Apple apple{}; return apple; }
代碼:
class Apple { public: Apple() { } ~Apple() { } Apple(const Apple& apple) { this->a = apple.a; this->b = apple.b; } Apple& operator=(const Apple& apple) { this->a = apple.a; this->b = apple.b; return *this; } void Print() { cout << a << " " << b << endl; } int a = 1; int b = 2; }; Apple GetApple() { Apple apple{}; return apple; }
構(gòu)造函數(shù)的執(zhí)行過(guò)程分析
構(gòu)造函數(shù)的執(zhí)行過(guò)程:即使是無(wú)參構(gòu)造函數(shù),調(diào)用之前仍然要傳參。傳入的是一個(gè)地址,需要在函數(shù)運(yùn)行結(jié)束的時(shí)候,這個(gè)地址上有了一個(gè)對(duì)象。
main 函數(shù)如下:
int main() { Apple a; int x = a.a; x = 10; return 0; }
生成的匯編代碼:
# main 函數(shù)構(gòu)造對(duì)象的地方: pushq %rbp # rbp 壓棧 movq %rsp, %rbp # rsp 替換 rbp pushq %rbx # rbx 壓棧,保存現(xiàn)場(chǎng) subq $24, %rsp # rsp 自減 24,棧向下增長(zhǎng),相當(dāng)于擴(kuò)展 24 字節(jié) leaq -24(%rbp), %rax # 將地址賦值給 rax,rax 是 Apple 對(duì)象開(kāi)始的地方 movq %rax, %rdi # rdi 傳參 call _ZN5AppleC1Ev movl -28(%rbp), %eax # 返回之后,會(huì)執(zhí)行 int x = a.a; movl %eax, -20(%rbp) # 取第一個(gè)字節(jié)賦值給 x movl $10, -20(%rbp) # 直接使用 10 賦值給 x;寫著行的目的只是為了確定 x 的位置 # 默認(rèn)構(gòu)造器: pushq %rbp # rbp 壓棧 movq %rsp, %rbp # rsp 替換 rbp movq %rdi, -8(%rbp) # rdi 是前面 rax 的值 movq -8(%rbp), %rax # 設(shè)置 rax 為 rbp 減 8 movl $1, (%rax) # 間接尋址,設(shè)置 4 字節(jié)為 1 movq -8(%rbp), %rax # rax 其實(shí)還是一樣的 movl $2, 4(%rax) # 變址尋址,其實(shí)就是間接尋址加一個(gè)偏移量,設(shè)置 4 字節(jié)為 2 nop popq %rbp ret # 于是最終 rdi 開(kāi)始,向下的 8 字節(jié)是 Apple 對(duì)象
返回對(duì)象函數(shù)的分析
從匯編的視角來(lái)看,調(diào)用構(gòu)造器和調(diào)用 “返回對(duì)象” 的函數(shù)是一樣的。它的執(zhí)行過(guò)程是:即使是兩個(gè)函數(shù)都是無(wú)參的,它仍然會(huì)進(jìn)行一次傳參。傳入的是一個(gè)地址,需要在函數(shù)調(diào)用結(jié)束后,這個(gè)地址上有了一個(gè)對(duì)象。從匯編的角度來(lái)看,對(duì)象就是一堆數(shù)據(jù)的排列,比如說(shuō)最普通的對(duì)象就是數(shù)據(jù)成員按照聲明順序直接排列。
舉個(gè)實(shí)際點(diǎn)的例子。Apple 這個(gè)類,有兩個(gè)成員,a 和 b。調(diào)用了構(gòu)造器或者 “返回對(duì)象” 的函數(shù),先傳入一個(gè)地址,之后函數(shù)里面會(huì)在這個(gè)地址上存放兩個(gè)數(shù),分別是 a 和 b 的值,然后返回。此時(shí),這個(gè)地址上就有了 a 和 b,雖然機(jī)器看不到對(duì)象,但是對(duì)我們來(lái)說(shuō),它就是一個(gè)對(duì)象。
如果關(guān)閉了 NRV 優(yōu)化,那么首先會(huì)傳入一個(gè)地址,然后構(gòu)造一個(gè)對(duì)象,這個(gè)對(duì)象將會(huì)被拷貝構(gòu)造到傳入的地址上,返回。之后如果調(diào)用了一次賦值,那么還要將這個(gè)地址上的對(duì)象復(fù)制構(gòu)造給棧上的變量。整個(gè)過(guò)程,實(shí)際上存在兩個(gè)臨時(shí)對(duì)象,發(fā)生了一次構(gòu)造、一次復(fù)制構(gòu)造、一次賦值構(gòu)造。
main: .LFB3535: pushq %rbp movq %rsp, %rbp pushq %rbx subq $40, %rsp leaq -36(%rbp), %rax movq %rax, %rdi call _ZN5AppleC1Ev movl -36(%rbp), %eax movl %eax, -20(%rbp) movl $10, -20(%rbp) leaq -28(%rbp), %rax # 這里之前的匯編是一樣的,調(diào)用 GetApple movq %rax, %rdi # rdi,傳參 call _Z8GetApplev leaq -28(%rbp), %rdx # rax 已經(jīng)存放對(duì)象,-28 的位置有對(duì)象 leaq -36(%rbp), %rax movq %rdx, %rsi movq %rax, %rdi call _ZN5AppleaSERKS_ leaq -28(%rbp), %rax movq %rax, %rdi call _ZN5AppleD1Ev movl $0, %ebx leaq -36(%rbp), %rax movq %rax, %rdi call _ZN5AppleD1Ev movl %ebx, %eax addq $40, %rsp popq %rbx popq %rbp # GetApple 函數(shù)的匯編代碼 pushq %rbp movq %rsp, %rbp subq $32, %rsp movq %rdi, -24(%rbp) # 分配空間,然后將傳過(guò)來(lái)的 rdi 放進(jìn)去 leaq -8(%rbp), %rax movq %rax, %rdi # 前面分析過(guò)了,調(diào)用回來(lái)時(shí) rax 開(kāi)始的 8 字節(jié)就是對(duì)象 call _ZN5AppleC1Ev leaq -8(%rbp), %rdx # 取 rax 地址到 rdx movq -24(%rbp), %rax # 取 rdi 地址到 rax movq %rdx, %rsi # rsi 已經(jīng)有對(duì)象了 movq %rax, %rdi # rsi 和 rdi 都用來(lái)傳參 call _ZN5AppleC1ERKS_ # 調(diào)用拷貝構(gòu)造函數(shù) leaq -8(%rbp), %rax # rax 上有對(duì)象了 movq %rax, %rdi # rdi 傳參,準(zhǔn)備調(diào)用析構(gòu)函數(shù) call _ZN5AppleD1Ev nop movq -24(%rbp), %rax # 前面調(diào)用拷貝構(gòu)造前的地址,這個(gè)地址有對(duì)象了 leave ret # 拷貝構(gòu)造函數(shù) pushq %rbp movq %rsp, %rbp movq %rdi, -8(%rbp) # rdi 無(wú)對(duì)象 movq %rsi, -16(%rbp) # rsi 有對(duì)象 movq -16(%rbp), %rax # 把對(duì)象放到 rax movl (%rax), %edx # 把第一屬性(4 字節(jié))移動(dòng)到 edx movq -8(%rbp), %rax movl %edx, (%rax) # 把第一屬性移動(dòng)到 rdi movq -16(%rbp), %rax movl 4(%rax), %edx # 把第二屬性移動(dòng)到 edx movq -8(%rbp), %rax movl %edx, 4(%rax) # 把第二屬性移動(dòng)到 rdi 上 movq -8(%rbp), %rax # rdi 上有了對(duì)象,rax 也設(shè)置一個(gè) popq %rbp ret
到此這篇關(guān)于C++:從匯編的視角看對(duì)象的創(chuàng)建的文章就介紹到這了,更多相關(guān)C++匯編對(duì)象內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++產(chǎn)生隨機(jī)數(shù)的實(shí)現(xiàn)代碼
本篇文章是對(duì)C++中產(chǎn)生隨機(jī)數(shù)的實(shí)現(xiàn)代碼進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C++ deque與vector對(duì)比的優(yōu)缺點(diǎn)
這篇文章主要介紹了C++中deque與vector相比的優(yōu)勢(shì)與劣勢(shì),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-01-01C++ opencv ffmpeg圖片序列化實(shí)現(xiàn)代碼解析
這篇文章主要介紹了C++ opencv ffmpeg圖片序列化實(shí)現(xiàn)代碼解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08