接下來(lái)我將持續(xù)更新“深度解讀《深度探索C++對(duì)象模型》”系列,敬請(qǐng)期待,歡迎關(guān)注!也可以關(guān)注公眾號(hào):iShare愛(ài)分享,自動(dòng)獲得推文和全部的文章列表。
在《深度解讀《深度探索C++對(duì)象模型》之C++對(duì)象的內(nèi)存布局》這篇文章中已經(jīng)詳細(xì)分析過(guò)C++的對(duì)象在經(jīng)過(guò)封裝后,在各種情況下的內(nèi)存布局以及增加的成本。本文將進(jìn)一步分析C++對(duì)象在封裝后,數(shù)據(jù)成員的存取的實(shí)現(xiàn)手段及訪(fǎng)問(wèn)的效率。在這里先拋出一個(gè)問(wèn)題,然后帶著問(wèn)題來(lái)一步一步分析,如下面的代碼:
class Point {};
Point p;
Point *pp = &p;
p.x = 0;
pp->x = 0;
上面的代碼中,對(duì)數(shù)據(jù)成員x的存取成本是什么?通過(guò)對(duì)象p來(lái)存取成員x和通過(guò)對(duì)象的指針pp來(lái)存取成員x的效率存在差異嗎?要搞清楚這個(gè)問(wèn)題,得看具體的Point類(lèi)的定義以及成員x的聲明方式。Point類(lèi)可能是一個(gè)獨(dú)立的類(lèi)(也就是沒(méi)有從其他類(lèi)繼承而來(lái)),也可能是一個(gè)單一繼承或者多重繼承而來(lái)的類(lèi),甚至也有可能它的繼承父類(lèi)中有一個(gè)是虛擬基類(lèi)(virtual base class),成員x的聲明可能是靜態(tài)的或者是非靜態(tài)的。下面的幾節(jié)將根據(jù)不同的情況來(lái)一一分析。
類(lèi)對(duì)象的數(shù)據(jù)成員的存取效率分析系列篇幅比較長(zhǎng),所以根據(jù)不同的類(lèi)的定義劃分為幾種情形來(lái)分析,這篇先來(lái)分析靜態(tài)數(shù)據(jù)成員的情況。
靜態(tài)數(shù)據(jù)成員在編譯器里的實(shí)現(xiàn)
在前面的文章中說(shuō)過(guò),類(lèi)中的靜態(tài)數(shù)據(jù)成員是跟類(lèi)相關(guān)的,而非跟具體的對(duì)象有關(guān),它存儲(chǔ)在對(duì)象之外,具體的存儲(chǔ)位置是在程序中的數(shù)據(jù)段中。它其實(shí)跟一個(gè)全局變量沒(méi)什么區(qū)別,在編譯期間編譯器就已經(jīng)確定好了它的存儲(chǔ)位置,所以能夠確定它的地址??匆幌孪旅娴拇a:
#include <cstdio>
int global_val = 1;
class Base {
public:
int b1;
static int s1;
};
int Base::s1 = 1;
int main() {
static int static_var = 1;
int local_var = 1;
Base b;
printf("&global_val = %p\n", &global_val);
printf("&static_var = %p\n", &static_var);
printf("&local_var = %p\n", &local_var);
printf("&b.b1 = %p\n", &b.b1);
printf("&b.s1 = %p\n", &b.s1);
return 0;
}
程序輸出的結(jié)果:
&global_val = 0x102d74000
&static_var = 0x102d74008
&local_var = 0x16d0933f8
&b.b1 = 0x16d0933f4
&b.s1 = 0x102d74004
可以看到全局變量global_val和局部靜態(tài)變量static_var以及類(lèi)中的靜態(tài)數(shù)據(jù)成員s1的地址是順序且緊密排列在一起的,而且跟其他的兩個(gè)局部變量的地址相差較大,說(shuō)明這幾個(gè)都是一起存儲(chǔ)在程序的數(shù)據(jù)段中的。類(lèi)中的非靜態(tài)數(shù)據(jù)成員b1跟局部變量local_var一樣,是存放在棧中的。
可以進(jìn)一步看看生成的匯編代碼,看一下是怎么存取靜態(tài)數(shù)據(jù)成員的,下面節(jié)選部分的匯編代碼:
main: # @main
# 略...
lea rdi, [rip + .L.str]
lea rsi, [rip + global_val]
mov al, 0
call printf@PLT
lea rdi, [rip + .L.str.1]
lea rsi, [rip + main::static_var]
mov al, 0
call printf@PLT
# 略...
lea rdi, [rip + .L.str.4]
lea rsi, [rip + Base::s1]
mov al, 0
call printf@PLT
# 略...
ret
global_val:
.long 1 # 0x1
Base::s1:
.long 1 # 0x1
main::static_var:
.long 1 # 0x1
從匯編代碼中看到,global_val、Base::s1和main::static_var是定義在數(shù)據(jù)段中的,在代碼中直接使用它們的地址,如:
lea rsi, [rip + Base::s1]
則是將Base::s1的地址加載到rsi寄存器中,作為參數(shù)傳遞給printf函數(shù)。這也證明了它跟全局變量,普通的靜態(tài)變量是沒(méi)有區(qū)別的。結(jié)論就是,類(lèi)中的靜態(tài)數(shù)據(jù)成員的存取方式是直接通過(guò)一個(gè)具體的地址來(lái)訪(fǎng)問(wèn)的,跟全局變量毫無(wú)區(qū)別,所以效率上也跟訪(fǎng)問(wèn)一個(gè)全局變量一樣。
通過(guò)不同方式存取靜態(tài)數(shù)據(jù)成員的效率差異
訪(fǎng)問(wèn)類(lèi)的靜態(tài)數(shù)據(jù)成員可以通過(guò)類(lèi)名來(lái)訪(fǎng)問(wèn),如Base::s1,也可以通過(guò)對(duì)象來(lái)訪(fǎng)問(wèn),如b.s1,甚至是通過(guò)指針來(lái)訪(fǎng)問(wèn),如pb->s1。那么這幾種訪(fǎng)問(wèn)方式有什么差別?或者說(shuō)是否有效率上的損失?其實(shí)這幾種訪(fǎng)問(wèn)方式本質(zhì)上沒(méi)有任何差別,編譯器會(huì)轉(zhuǎn)換成如Base::s1一樣的方式,后面的兩種方式只是語(yǔ)法上的方便而已,看一下匯編代碼就一目了然。把上面的例子多余的代碼刪除掉,只留下Base類(lèi),然后main函數(shù)中增加幾行打印,如下:
Base b;
Base *pb = &b;
printf("&Base::s1 = %p\n", &Base::s1);
printf("&b.s1 = %p\n", &b.s1);
printf("&pb->s1 = %p\n", &pb->s1);
輸出的結(jié)果當(dāng)然是同一個(gè)地址了,下面是節(jié)選的匯編代碼:
lea rdi, [rip + .L.str]
lea rsi, [rip + Base::s1]
mov al, 0
call printf@PLT
lea rdi, [rip + .L.str.1]
lea rsi, [rip + Base::s1]
mov al, 0
call printf@PLT
lea rdi, [rip + .L.str.2]
lea rsi, [rip + Base::s1]
mov al, 0
call printf@PLT
可以看到C++中的幾行不同的訪(fǎng)問(wèn)方式在匯編代碼中都轉(zhuǎn)換為同樣的代碼:
lea rsi, [rip + Base::s1]
繼承而來(lái)的靜態(tài)數(shù)據(jù)成員的存取分析
我們已經(jīng)知道類(lèi)中的靜態(tài)數(shù)據(jù)成員是跟對(duì)象無(wú)關(guān)的,所有的對(duì)象都共享同一個(gè)靜態(tài)數(shù)據(jù)成員。但是如果繼承而來(lái)的靜態(tài)數(shù)據(jù)成員又是怎樣的呢?假如定義一個(gè)Derived類(lèi),它是Base類(lèi)的派生類(lèi),那么靜態(tài)數(shù)據(jù)成員s1的情況又是如何?其實(shí)無(wú)論繼承多少次,靜態(tài)數(shù)據(jù)成員都只有一份,無(wú)論是Derived類(lèi)還是Base類(lèi),它們都共享同一個(gè)靜態(tài)數(shù)據(jù)成員s1,可以通過(guò)下面的例子來(lái)驗(yàn)證一下:
#include <cstdio>
class Base {
public:
int b1;
static int s1;
};
int Base::s1 = 1;
class Derived: public Base {};
int main() {
Derived d;
printf("&d.s1 = %p\n", &d.s1);
printf("d.s1 = %d\n", d.s1);
d.s1 = 2;
Base b;
printf("&b.s1 = %p\n", &b.s1);
printf("b.s1 = %d\n", b.s1);
return 0;
}
程序輸出的結(jié)果:
&d.s1 = 0x10028c000
d.s1 = 1
&b.s1 = 0x10028c000
b.s1 = 2
可以看到通過(guò)Derived類(lèi)的對(duì)象d和Base類(lèi)的對(duì)象b訪(fǎng)問(wèn)到的都是同一個(gè)地址,通過(guò)對(duì)象d修改s1后,通過(guò)對(duì)象b可以看到修改后的值。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-855039.html
如果您感興趣這方面的內(nèi)容,請(qǐng)?jiān)谖⑿派纤阉鞴娞?hào)iShare愛(ài)分享并關(guān)注,以便在內(nèi)容更新時(shí)直接向您推送。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-855039.html
到了這里,關(guān)于深度解讀《深度探索C++對(duì)象模型》之?dāng)?shù)據(jù)成員的存取效率分析(一)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!