欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

C++對(duì)象模型:g++的實(shí)現(xiàn)(一)

剛看完了《深度探索C++對(duì)象模型》第三章,這里做一下總結(jié),也寫一下我自己在g++ 7.5.0上的驗(yàn)證。
本文中所有的源文件都可以在這里拿到(百度網(wǎng)盤鏈接)。
注意,這里所說(shuō)的“對(duì)象”是指在C++中使用classstruct關(guān)鍵字創(chuàng)建的類的實(shí)例。

創(chuàng)新互聯(lián)建站-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比河南網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫(kù),直接使用。一站式河南網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋河南地區(qū)。費(fèi)用合理售后完善,10余年實(shí)體公司更值得信賴。

1. 無(wú)繼承情況下的C++對(duì)象內(nèi)存布局

首先當(dāng)然是從最基礎(chǔ)的情況來(lái)講,在沒有繼承的情況下的C++對(duì)象內(nèi)存布局是什么樣的?這又分為兩種:無(wú)虛函數(shù)和有虛函數(shù)。

1.1 無(wú)虛函數(shù)

C++類內(nèi)成員變量分為兩類:static成員變量和非static成員變量。static成員變量不在類的實(shí)例的內(nèi)部,在整個(gè)內(nèi)存中只有一份,只需要使用類名即可訪問(wèn);而非static成員變量在類的實(shí)例內(nèi)部,需要為其分配空間。
在這種情況下C++的對(duì)象和C的結(jié)構(gòu)體是一樣的,畢竟要實(shí)現(xiàn)和C的兼容,主要就是結(jié)構(gòu)體/類內(nèi)成員變量的對(duì)齊。
其一般規(guī)則總結(jié)如下:

  1. 所有成員按照在類內(nèi)的聲明順序在內(nèi)存中排列;
// test00.cpp
#include <iostream>

int main();

class Test00 {
    friend int main();
public:
    int i1;
private:
    int i2;
public:
    int i3;
};

#define showOffset(ClassName, memberName) (reinterpret_cast<unsigned long>( &(static_cast<ClassName*>(nullptr)->memberName)))

int main() {
    std::cout << showOffset(Test00, i1) << std::endl;
    std::cout << showOffset(Test00, i2) << std::endl;
    std::cout << showOffset(Test00, i3) << std::endl;
}

// Output:
//  0
//  4
//  8
  1. 任一非static成員變量的偏移(offset)要是其大小的倍數(shù);
// test01.cpp
#include <iostream>
struct Test01 {
    char c;
    int i; // 如果緊湊排列,則i的偏移為1,但i的size為4,偏移要是4的倍數(shù),因此i的偏移為4
};

#define showOffset(ClassName, memberName) (reinterpret_cast<unsigned long>( &(static_cast<ClassName*>(nullptr)->memberName)))

int main() {
    std::cout << showOffset(struct Test01, i) << std::endl;
}

// Output:
//  4
  1. 結(jié)構(gòu)體整體的size需要為最大非static成員變量size的倍數(shù);
// test02.cpp
#include <iostream>

// 如果緊湊排,Test02_1的size應(yīng)為9,
// 但要與int(size為4)對(duì)其,所以其size為12
struct Test02_1 {
    char c1;  // Offset: 0
    int i;    // Offset: 4
    char c2;  // Offset: 8
};

// Test02_2成員和Test02_1相同,但順序不同,
// 受規(guī)則2和3影響,其size為8
struct Test02_2 {
    char c1;  // Offset: 0
    char c2;  // Offset: 1
    int i;    // Offset: 4
};

int main() {
    std::cout << "sizeof Test02_1: " << sizeof(Test02_1) << std::endl;
    std::cout << "sizeof Test02_2: " << sizeof(Test02_2) << std::endl;
}

// Output:
//  sizeof Test02_1: 12
//  sizeof Test02_2: 8
  1. 空對(duì)象的size為1,為了保證每個(gè)對(duì)象都有唯一的內(nèi)存位置(memory location)
// test03.cpp
#include <iostream>

struct Test03 {}; // Empty class

int main() {
    Test03 a, b;
    std::cout << "sizeof Test03: " << sizeof(Test03) << std::endl;
    if (&a == &b)
        std::cerr << " Error! &a == &b, at " << static_cast<void*>(&a) <<  std::endl;
    else
        std::cout << "a and b has different address, &a = " << static_cast<void*>(&a) << " and &b = " << static_cast<void*>(&b) << std::endl;
}

// Output:
//  sizeof Test03: 1
//  a and b has different address, &a = 0x7fffe62e8486 and &b = 0x7fffe62e8487
  1. 當(dāng)類(class)/結(jié)構(gòu)體(struct) A 作為一個(gè)類B的內(nèi)部成員變量時(shí),其對(duì)齊要求為類A內(nèi)部最大的對(duì)齊要求;
// test04.cpp
#include <iostream>

// 規(guī)則2中的類,size為8,對(duì)齊要求為4
struct Test01{
    char c;
    int i;
};

struct Test04 {
    char c;   // Offset: 1, size 1
    Test01 t; // Offset: 4, size 8
};

#define showOffset(ClassName, memberName) (reinterpret_cast<unsigned long>( &(static_cast<ClassName*>(nullptr)->memberName)))

int main() {
    std::cout << "Offset of t in struct Test04: " << showOffset(Test04, t) << std::endl;
    std::cout << "sizeof Test04: " << sizeof(Test04) << std::endl; 
}

// Output:
//  Offset of t in struct Test04: 4
//  sizeof Test04: 12
  1. 空的類(empty class)A作為作為一個(gè)類B的成員變量時(shí),類A占用一個(gè)字節(jié)的空間,對(duì)其要求也為1;
// test05.cpp
#include <iostream>

// 規(guī)則4中的類,空類,size = 1
struct Test03 {};

struct Test05 {
    char c;     // Offset: 0, size: 1
    Test03 t;   // Offset: 1, size: 1
};

#define showOffset(ClassName, memberName) (reinterpret_cast<unsigned long>( &(static_cast<ClassName*>(nullptr)->memberName)))

int main() {
    std::cout << "Offset of t in struct Test04: " << showOffset(Test05, t) << std::endl;
    std::cout << "sizeof Test05: " << sizeof(Test05) << std::endl; 
}

// Output:
//  Offset of t in struct Test04: 1
//  sizeof Test05: 2

1.2 有虛函數(shù)

C++使用虛函數(shù)來(lái)實(shí)現(xiàn)多態(tài),非虛函數(shù)不展現(xiàn)多態(tài)性,當(dāng)調(diào)用非虛函數(shù)時(shí),只要調(diào)用一個(gè)寫死的地址即可,無(wú)論是使用對(duì)象調(diào)用還是使用指針/引用調(diào)用;而當(dāng)使用指針/引用調(diào)用虛函數(shù)需要視其綁定到的實(shí)際對(duì)象來(lái)調(diào)用對(duì)應(yīng)的虛函數(shù),以展現(xiàn)多態(tài)性(用對(duì)象調(diào)用虛函數(shù)不展現(xiàn)多態(tài)性)。
而C++實(shí)現(xiàn)虛函數(shù)用到的便是虛表。所謂虛表,就是保存該類所有虛函數(shù)地址的一張表,一個(gè)類的某個(gè)確定的虛函數(shù)在虛表的確定位置,而類實(shí)例中有一個(gè)虛表指針指向該虛表,當(dāng)出現(xiàn)類繼承并覆寫(override)了該虛函數(shù)時(shí),只需要將虛表指針指向另一張?zhí)摫恚撎摫碇袑?duì)應(yīng)位置的函數(shù)指針換為新的函數(shù)即可。另外,一個(gè)類的所有對(duì)象共享同一張?zhí)摫?,因此不?huì)帶來(lái)大的內(nèi)存消耗。該虛表由編譯器生成。
這里只是對(duì)于虛函數(shù)和虛表進(jìn)行了簡(jiǎn)單的描述,詳細(xì)可查詢網(wǎng)絡(luò)資源,這里不再贅述。
就像上面所說(shuō),相比于沒有虛函數(shù)的類,由虛函數(shù)的類的實(shí)例只是多了一個(gè)指向虛表的指針,其放在類的開頭或者結(jié)尾(g++將其放在類的開頭),大小和對(duì)其要求視平臺(tái)而定,在x86-64平臺(tái)上,虛表指針大小和對(duì)其要求為8字節(jié)。

// test06.cpp
class Point {
public:
    Point(int x)
        :m_x(x)
    {}

    virtual
    int getX()
    { return m_x; }

private:
    int m_x;
};


int main() {
    Point p(1);
    int x = p.getX();
}


使用gdb觀察,可以看到Point類實(shí)例p的size為16,包括size為8的虛表指針和size為4的int類型的成員變量m_x,同時(shí),由于虛表指針的對(duì)其要求為8,所以Point的size必須是8的倍數(shù),所以其size為16。
同時(shí)查看p的內(nèi)存布局,可以看到虛表指針被放置于類實(shí)例的頭部,占用8個(gè)字節(jié),后面緊跟4個(gè)字節(jié)的int類型的成員變量m_i,最后填充了4個(gè)字節(jié)以使類Point的size為8的倍數(shù)。

我們?cè)诓榭匆幌绿摫碇羔樦赶虻膬?nèi)存,我這里使用的是64位系統(tǒng)和程序,所以函數(shù)指針是8位大小,虛表指針指向的虛表的第一個(gè)表項(xiàng)是地址0x0b2,同時(shí)查看反匯編,因?yàn)槲覀兪褂脤?duì)象來(lái)調(diào)用虛函數(shù),不展現(xiàn)多態(tài)性,這里直接call了Point::getX()的地址,可以看到其地址為0x0b2,正好是前面虛表的第一個(gè)表項(xiàng)。
還有就是Point類型對(duì)應(yīng)的typeinfo對(duì)象的地址,在《深度探索C++對(duì)象模型》中提到其位于虛表的第一個(gè)表項(xiàng),但前面我們看到虛表第一個(gè)表項(xiàng)存放的是虛函數(shù),那typeinfo的地址放在哪里呢?我們來(lái)找一下。

// file test07.cpp
#include <typeinfo>
  2 class Point {
  3 public:
  4     Point(int x)
  5         :m_x(x)
  6     {}
  7
  8     virtual
  9     int getX()
 10     { return m_x; }
 11
 12 private:
 13     int m_x;
 14 };
 15
 16 int main() {
 17     Point p(1);
 18     auto& ti = typeid(p);
 19     int x = p.getX();
 20 }


可以看到反匯編中保存了0x8200da8這一地址到棧上,再結(jié)合我們的源碼,很可能gdb所提示的<_ZTI5Point>這一對(duì)象就是Point類的typeinfo對(duì)象,我們使用工具c++filt來(lái)看_ZTI5Point這個(gè)被修飾過(guò)的符號(hào)是什么含義,不出所料,正是Point類對(duì)應(yīng)的typeinfo對(duì)象。

liuyun@DESKTOP-Q5AT31V:/tmp/test/cppObjectModel/chap03/blog$ c++filt _ZTI5Point
typeinfo for Point

既然Point對(duì)應(yīng)的typeinfo對(duì)象的地址為0x8200da8,我們查看虛表附近的地址,發(fā)現(xiàn)虛表指針指向的地址的前面的一個(gè)QWORD的內(nèi)容正好是typeinfo的地址,那是不是虛表指針指向的并不是虛表的開頭,而是第一個(gè)虛函數(shù)所在的地址,而在虛表中,第一個(gè)虛函數(shù)這一表項(xiàng)前面便是該類對(duì)應(yīng)的typeinfo的地址?

在查閱資料的時(shí)候,《C++虛函數(shù)之二:虛函數(shù)表與虛函數(shù)調(diào)用》這篇博客提到g++支持-fdump-class-hierarchy這一編譯選項(xiàng),可以生成一個(gè)名為{source_file_name}.002t.class的文件,文件中詳細(xì)記錄了各個(gè)類的信息,包括其虛表信息。
正如我們所想,如果我們使用vptr指代虛表指針,那么vptr[0]就是第一個(gè)虛函數(shù)的地址,vptr[-1]則是該類對(duì)應(yīng)的typeinfo的地址,而在最前面,g++還填充了一個(gè)空的表項(xiàng)。

最后還有一個(gè)問(wèn)題,再?zèng)]有虛函數(shù)的時(shí)候,編譯器為了讓每一個(gè)對(duì)象都有自己獨(dú)一無(wú)二的地址,會(huì)在對(duì)象中插入一個(gè)字節(jié)占位,而在有虛函數(shù)的時(shí)候類中會(huì)有一個(gè)原生的虛表指針vptr,從而至少占8字節(jié)大小(x86-64上),那么是否就不需要再插入一個(gè)字節(jié)了呢?事實(shí)正如我們所想,Test08類的size為8而不是16。

// test08.cpp
#include <iostream>

class Test08 {
public:
virtual
int getNumber() { return s_i++; }

private:
    static int s_i;
};

int Test08::s_i = 0;

int main() {
    std::cout << "sizeof Test08: " << sizeof(Test08) << std::endl;
    Test08 t;
    int i = t.getNumber();
}
// Output:
//  sizeof Test08: 8

這一篇博客就先寫到這里,下一篇再談?wù)勗诶^承體系下g++是如何實(shí)現(xiàn)C++對(duì)象的內(nèi)存布局的。

當(dāng)前題目:C++對(duì)象模型:g++的實(shí)現(xiàn)(一)
文章URL:http://chinadenli.net/article8/dsoieip.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站改版、搜索引擎優(yōu)化建站公司、響應(yīng)式網(wǎng)站網(wǎng)站內(nèi)鏈、網(wǎng)站設(shè)計(jì)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站制作