為什麼 C 中成員函式指標是 16 位元組?

2021-06-27 13:10:59 字數 3690 閱讀 3243

當我們討論指標時,通常假設它是一種可以用void *指標來表示的東西,在 x86_64 平台下是 8 個位元組大小。例如,下面是來自 維基百科中關於 x86_64 的文章 的摘錄:

pushes and pops on the stack are always in 8-byte strides, andpointers are 8 bytes wide.

從 cpu 的角度來看,指標無非就是記憶體的位址,所有的記憶體位址在 x86_64 平台下都是由 64 位來表示,所以假設它是 8 個位元組是正確的。通過簡單輸出不同型別指標的長度,這也不難驗證我們所說的。

#include 

intmain

()

編譯執行上面的程式,從結果中可以看出所有的指標的長度都是 8 個位元組:

$ uname -i

x86_64

$ g++ -wall ./example.cc

$ ./a.out

sizeof(int*)

== 8

sizeof(double*)

== 8

sizeof(void(*)())

== 8

然而在 c++ 中還有一種特例——成員函式的指標。很有意思吧,成員函式指標是其它任何指標長度的兩倍。這可以通過下面簡單的程式來驗證,輸出的結果是 「16」:

#include 

struct

foo};

intmain

()

這是否以為著維基百科上錯了呢?顯然不是!從硬體的角度來看,所有的指標仍然是 8 個位元組。既然如此,那麼成員函式的指標是什麼呢?這是 c++ 語言的特性,這裡成員函式的指標不是直接對映到硬體上的,它由執行時(編譯器)來實現,會帶來一些額外的開銷,通常會導致效能的損失。c++ 語言規範中並沒有提到實現的細節,也沒有解釋這種型別指標。幸運的是,itanium c++ abi 規範中共享了 c++ 執行時實現的細節——舉例來說,它解釋了 virtual table、rtti 和異常是如何實現的,在 §2.3 中也解釋了成員指標:

a pointer to member function is a pair as follows:

ptr:

for a non-virtual function, this field is a ****** function pointer. for a virtual function, it is 1 plus the virtual table offset (in bytes) of the function, represented as a ptrdiff_t. the value zero represents a null pointer, independent of the adjustment field value below.

adj:

the required adjustment to this, represented as a ptrdiff_t.

所以,成員指標是 16 位元組而不是 8 位元組,因為在簡單函式指標的後面還需要儲存怎樣調整 「this" 指標(總是隱式地傳遞給非靜態成員函式)的資訊。 abi 規範並沒有說為什麼以及什麼時候需要調整 this 指標。可能一開始並不是很明顯,讓我們先看下面類繼承的例子:

struct

achar

pad0[32

];};

struct

bchar

pad2[64

];};

structc:

a,b;

a 和 b 都有乙個非靜態成員函式以及乙個資料成員。這兩個方法可以通過隱式傳遞給它們的 「this" 指標來訪問到它們類中的資料成員。為了訪問到任意的資料成員,需要在 "this" 指標上加上乙個偏移,偏移是資料成員到類物件基址的偏移,可以由 ptrdiff_t 來表示。然而事情在多重繼承時將會變得更複雜。我們有乙個類 c 繼承了 a 和 b,將會發生什麼呢?編譯器將 a 和 b 同時放到記憶體中,b 在 a 之下,因此,a 類的方法和 b 類的方法看到的 this 指標的值是不一樣的。這可以通過實踐來簡單驗證,如:

#include 

struct

achar

pad0[32

];};

struct

bchar

pad2[64

];};

structc:

a,b;

intmain

()

$ g++ -wall -o test ./test.cc && ./test

a's this: 0x7fff57ddfb48

b's this: 0x7fff57ddfb68

正如你看到的,「this」 指標的值傳給 b 的方法要比 a 的方法要大 32 位元組——乙個類 a 物件的實際大小。但是,當我們用下面的函式通過指標來呼叫類 c 的方法時,會發生什麼呢?

void

call_by_ptr

(constc&

obj,

void(c

::*mem_func

)()const

)

與呼叫什麼函式有關,不同的 "this" 指標值會被傳遞到這些函式中。但是call_by_ptr函式並不知道它的引數是foo()的指標還是bar()的指標,能知道該資訊的唯一時機是這些方法使用時。這就是為什麼成員函式的指標在呼叫之前需要知道如何調整this指標。現在,我們將所有的放到乙個簡單的程式,闡釋了內部工作的機制:

#include 

struct

achar

pad0[32

];};

struct

bchar

pad2[64

];};

structc:

a,b;

void

call_by_ptr

(constc&

obj,

void(c

::*mem_func

)()const

)int

main

()

上面的程式輸出如下:

------------------------------

object ptr: 0x7fff535dfb28

function ptr: 0x10c620cac

pointer adj: 0

a's this: 0x7fff535dfb28

------------------------------

object ptr: 0x7fff535dfb28

function ptr: 0x10c620cfe

pointer adj: 0x20

b's this: 0x7fff535dfb48

希望本文能使問題變得更明確一點。

文章出自:

為什麼 C 中成員函式指標是 16 位元組?

當我們討論指標時,通常假設它是一種可以用void 指標來表示的東西,在 x86 64 平台下是 8 個位元組大小。例如,下面是來自 維基百科中關於 x86 64 的文章 的摘錄 pushes and pops on the stack are always in 8 byte strides,and...

靜態成員函式為什麼不能是虛函式

首先什麼是static靜態成員函式?靜態成員函式不屬於類中的任何乙個物件和例項,屬於類共有的乙個函式。也就是說,它不能用this指標來訪問,因為this指標指向的是每乙個物件和例項。對於virtual虛函式,它的呼叫恰恰使用this指標。在有虛函式的類例項中,this指標呼叫vptr指標,指向的是v...

C 為什麼要引入成員函式?

問題引入 程式設計提示使用者輸入圓的半徑,計算輸出圓的面積 include include intmain void 物件導向首先需要對問題進行抽象,定義乙個描述圓的資料型別,再建立具體的圓物件。在c語言中,可以定義乙個結構體struct,用來包含圓的半徑等資訊,而c 中稱為類class。類和結構體...