成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

寫了八年 C++,才知道 this 指針竟是這樣工作的!從匯編看本質!

開發
今天咱們直接掀開 C++ 的蓋子,從匯編代碼的角度看看 this 指針到底是個啥玩意兒!我們不僅會了解它的基本概念,還會深入探索它在不同編譯器、不同調用約定下的表現,以及它與 C++ 高級特性的關系。

大家好,我是小康。今天我們來聊聊 C++ 的 this 指針。

相信我,看完這篇文章,你將徹底搞懂 C++ 中最神秘的 this 指針!不再被面試官問到 this 時一臉茫然!

如果你對 this 指針還不太熟悉,強烈推薦先看看這篇入門文章:C++ 中的 this 指針:你不知道的 5 個小秘密! 打好基礎后,再回來啃這篇硬核分析,絕對事半功倍!

一、前言:this指針,C++中的隱形殺手

嘿,朋友們!還記得第一次接觸 C++ 的 this 指針時的懵逼感覺嗎?

  • "為啥要用this?"
  • "它到底指向哪里?"
  • "為啥我不寫 this 也能訪問成員變量?"
  • "編譯器是怎么處理這個神秘的指針的?"

如果你還在為這些問題撓頭,那這篇文章就是為你準備的!今天咱們不搞那些抽象的概念解釋,直接掀開 C++ 的蓋子,從匯編代碼的角度看看 this 指針到底是個啥玩意兒!我們不僅會了解它的基本概念,還會深入探索它在不同編譯器、不同調用約定下的表現,以及它與 C++ 高級特性的關系。

二、this指針的真面目:一個隱藏的函數參數

先說個大實話:this指針其實就是編譯器偷偷塞給你的一個函數參數,它指向當前對象的內存地址。

是不是覺得有點懵?沒關系,咱們用一個超簡單的例子來說明:

class Dog {
public:
    int age;
    
    void bark() {
        cout << "汪汪,我今年" << age << "歲了!" << endl;
    }
};

int main() {
    Dog xiaohua;
    xiaohua.age = 3;
    xiaohua.bark();  // 輸出:汪汪,我今年3歲了!
    
    return0;
}

當我們調用xiaohua.bark()時,編譯器實際上做了什么呢?它悄悄地把這個調用轉換成了:

// 編譯器內部轉換后的代碼(偽代碼)
bark(&xiaohua);

也就是說,編譯器偷偷把對象的地址作為第一個參數傳給了成員函數!這個隱藏的參數,就是 this 指針!

三、揭秘:從匯編代碼看this指針

不信?那我們直接看匯編代碼!(別慌,我會用大白話解釋)

假設我們有這樣一個簡單的類:

class Counter {
private:
    int count;
public:
    Counter() : count(0) {}
    
    void increment() {
        count++;
    }
    
    int getCount() {
        return count;
    }
};

int main() {
    Counter c;
    c.increment();
    cout << c.getCount() << endl;
    return0;
}

我們可以用編譯器將這段代碼轉成匯編語言。以下是在MSVC編譯器(VS)編譯后的簡化匯編代碼:

; Counter::increment() 方法的匯編代碼(簡化版)
?increment@Counter@@QAEXXZ:          ; Counter::increment
    mov eax, ecx             ; ECX寄存器中存儲的是this指針
    mov edx, DWORD PTR [eax] ; 將this->count的值加載到EDX
    add edx, 1               ; count值加1
    mov DWORD PTR [eax], edx ; 將結果寫回this->count
    ret                      ; 返回

看到了嗎?在 Microsoft 的 x86 調用約定中,ECX寄存器被用來存儲類成員函數的 this 指針。在這段匯編代碼中,ecx就包含了我們對象c的內存地址!

如果我們切換到 G++ 編譯器和 Linux 平臺,匯編代碼可能看起來略有不同:

; G++下的Counter::increment()方法(簡化版)
_ZN7Counter9incrementEv:
    mov eax, DWORD PTR [edi]  ; 在 G++中,EDI寄存器存儲this指針
    add eax, 1                ; count值加1
    mov DWORD PTR [edi], eax  ; 將結果寫回this->count
    ret                       ; 返回

有趣的是,不同的編譯器和平臺對 this 指針的處理方式略有不同。這就是為什么理解底層機制如此重要——它讓我們能夠更好地理解跨平臺編程時可能遇到的問題。

四、深入探索:this指針是怎么傳遞的?

說到這里,你可能會好奇:"既然 this 是個參數,編譯器是怎么傳給函數的呢?"

這個問題涉及到所謂的"調用約定"。別被這個術語嚇到,簡單來說,調用約定就是"函數參數傳遞的規則",就像不同國家有不同的交通規則一樣。

讓我用一個簡單的比喻:函數調用就像寄快遞,調用約定就是快遞公司的送貨規則:

  • 參數應該放在哪里?(寄存器還是內存棧)
  • 參數應該以什么順序傳遞?(從左到右還是從右到左)
  • 誰負責"打掃現場"?(誰來清理棧)

對于 C++ 中的 this 指針,這個"快遞"有著特殊的送貨方式,而且在不同平臺上規則還不一樣!

1. 看個實際例子

為了讓概念更具體,我們來看一個簡單的類和它的成員函數調用:

class Dog {
public:
    int age;
    
    void bark() {
        cout << "汪汪,我今年" << age << "歲了!" << endl;
    }
    
    void eat(int foodAmount, bool isHungry) {
        if (isHungry) {
            cout << "真香!我吃了" << foodAmount << "克狗糧!" << endl;
        } else {
            cout << "我不餓,只吃了" << foodAmount/2 << "克。" << endl;
        }
    }
};

int main() {
    Dog dog;
    dog.age = 3;
    dog.bark();
    dog.eat(100, true);
    return0;
}

當我們調用dog.bark()和dog.eat(100, true)時,編譯器在不同平臺上的處理方式有什么不同呢?

2. Windows平臺(32位)下this指針的傳遞

在Windows 32位系統下,MSVC編譯器會這樣處理:

this指針放在哪里? → ECX寄存器
其他參數怎么傳? → 從右到左壓入棧中
誰來清理棧? → 被調用函數負責清理棧(稱為callee-clean,通過ret N指令實現)

當調用dog.eat(100, true)時,簡化的匯編代碼會是這樣:

; 從右到左壓棧,先壓入isHungry參數
push 1               ; true
; 再壓入foodAmount參數
push 100             ; 100克狗糧
; this指針(dog對象的地址)放入ECX寄存器
lea ecx, [dog]       ; 加載dog的地址到ECX
; 調用函數
call Dog::eat        ; 調用eat方法
; 函數內部會在返回前清理棧

3. Linux平臺(32位)下this指針的傳遞

在Linux 32位系統下,G++編譯器的處理方式有所不同:

this指針放在哪里? → 作為第一個參數,最后壓入棧
其他參數怎么傳? → 從右到左壓入棧中
誰來清理棧? → 對于普通成員函數,使用的是 cdecl 約定,由調用者清理棧

當調用dog.eat(100, true)時,簡化的匯編代碼會是:

; 從右到左壓棧,先壓入isHungry參數 
push 1               ; true
; 再壓入foodAmount參數
push 100             ; 100克狗糧
; 最后壓入this指針
push [dog的地址]      ; this指針
; 調用函數
call _ZN3Dog3eatEib  ; 調用eat方法,這是G++的名稱修飾(name mangling)
; 函數返回后,調用者清理棧
add esp, 12          ; 清理3個參數(each 4字節)

4. 64位系統下 this 指針的傳遞

在 64 位系統中,參數傳遞方式變得更加統一,主要通過寄存器完成,但 Windows 和 Linux 平臺使用的寄存器和規則有所不同:

(1) Windows 64位(MSVC編譯器):

  • this 指針放在 RCX 寄存器(第一個參數位置)
  • 后續參數分別放在 RDX, R8, R9 寄存器
  • 多余參數(超過4個)才會壓棧
  • 誰來清理棧?→ 調用者負責清理棧(通過 add rsp, N 指令來實現)

(2) Linux 64位(G++編譯器):

  • this 指針放在 RDI 寄存器(第一個參數位置)
  • 后續參數分別放在 RSI, RDX, RCX, R8, R9 寄存器
  • 多余參數(超過6個)才會壓棧
  • 誰來清理棧?→ 調用者負責清理棧(通過 add rsp, N 指令來實現)

以Windows 64位為例,調用dog.eat(100, true)時的簡化匯編:

; this指針放入RCX
lea rcx, [dog]       ; 加載dog對象地址到RCX
; foodAmount放入RDX
mov rdx, 100         ; 100放入RDX
; isHungry放入R8
mov r8, 1            ; true放入R8
; 調用函數
call Dog::eat
; 函數返回后,如果有參數通過棧傳遞,調用者需要清理棧
; 在這個例子中,所有參數都通過寄存器傳遞,不需要棧清理

這里有個有趣的變化:在 32 位系統中,Windows 和 Linux 對 this 指針的處理方式差異很大(寄存器vs棧),而在64位系統中,兩者都使用寄存器傳遞 this 指針,只是使用的具體寄存器不同。

另外,64 位系統無論 Windows 還是 Linux,都使用統一的調用約定,不再像 32 位平臺那樣對成員函數和普通函數使用不同的約定。這使得 64位 平臺下的函數調用機制更加一致和簡潔。

五、this指針到底有啥用?實用案例詳解

你可能會問:"那我為啥要關心 this 指針啊?又不是我自己寫的。"

好問題!this 指針雖然是編譯器偷偷加的,但了解它有這些超實用的好處:

1. 區分同名變量

當成員變量和函數參數同名時,this可以明確指向成員變量:

class Person {
private:
    string name;
    int age;
public:
    void setInfo(string name, int age) {
        this->name = name;  // 區分成員變量和參數
        this->age = age;    // 沒有this就會造成歧義
    }
};

2. 實現鏈式編程

返回 this 指針可以實現方法的連續調用,這是很多現代 C++ 庫的常用技巧:

class StringBuilder {
private:
    string data;
public:
    StringBuilder& append(const string& str) {
        data += str;
        return *this;  // 返回對象本身
    }
    
    StringBuilder& appendLine(const string& str) {
        data += str + "\n";
        return *this;
    }
    
    string toString() const {
        return data;
    }
};

// 使用方式
StringBuilder builder;
string result = builder.append("Hello").append(" ").append("World").appendLine("!").toString();
// 結果: "Hello World!\n"

這種編程風格在很多現代框架中非常常見,比如jQuery、C#的LINQ、Java的Stream API等。

3. 在構造函數初始化列表中使用

this指針在構造函數初始化列表中也很有用:

class Rectangle {
private:
    int width;
    int height;
    int area;
public:
    Rectangle(int width, int height) : 
        width(width),       // 參數width賦值給成員變量width
        height(height),     // 參數height賦值給成員變量height
        area(this->width * this->height)  // 使用已初始化的成員計算area
    {
        // 構造函數體
    }
};

注意在初始化列表中,成員變量是按照 聲明順序 初始化的,而不是按照初始化列表中的順序。上面的例子中,如果 area 在 width 和 height 之前聲明,那么計算 area 時使用的 width 和 height 將是未初始化的值!

4. 實現單例模式

this指針在實現單例模式時也非常有用:

class Singleton {
private:
    static Singleton* instance;
    
    // 私有構造函數
    Singleton() {}
    
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
    
    // 返回this的方法可以鏈式調用
    Singleton* doSomething() {
        cout << "Doing something..." << endl;
        return this;
    }
    
    Singleton* doSomethingElse() {
        cout << "Doing something else..." << endl;
        return this;
    }
};

// 初始化靜態成員
Singleton* Singleton::instance = nullptr;

// 使用方式
Singleton::getInstance()->doSomething()->doSomethingElse();

六、匯編角度看不同對象調用同一方法

讓我們更進一步,看看不同對象調用同一個方法時,this指針有什么不同:

int main() {
    Dog dog1, dog2;
    dog1.age = 3;
    dog2.age = 5;
    
    dog1.bark();  // 汪汪,我今年3歲了!
    dog2.bark();  // 汪汪,我今年5歲了!
}

從匯編角度來看,這兩次調用使用的是完全相同的指令,唯一的區別是傳入的 this 指針不同:

; dog1.bark()調用
lea ecx, [dog1]   ; 將dog1的地址加載到ECX(this指針)
call Dog::bark    ; 調用bark方法

; dog2.bark()調用
lea ecx, [dog2]   ; 將dog2的地址加載到ECX(this指針)
call Dog::bark    ; 調用相同的bark方法

這就解釋了為什么C++可以用同一份成員函數代碼處理不同的對象——因為函數通過 this 指針就能知道它正在操作哪個對象!

七、this指針與C++的高級特性

1. this指針與虛函數

虛函數是 C++ 多態的基礎,而this指針在虛函數調用中扮演著關鍵角色

看個簡單的多態例子:

class Animal {
public:
    virtual void makeSound() {
        cout << "動物發出聲音..." << endl;
    }
};

class Dog :public Animal {
public:
    void makeSound() override {
        cout << "汪汪汪!" << endl;
    }
};

void letAnimalSpeak(Animal* animal) {
    animal->makeSound();  // 調用虛函數
}

int main() {
    Dog dog;
    letAnimalSpeak(&dog);  // 輸出:汪汪汪!
}

這里 this 指針有什么作用呢?在虛函數調用中,this指針主要完成兩件事:

  • 找到正確的函數地址:當調用animal->makeSound()時,編譯器通過 this 指針找到對象的虛函數表,再從表中找到正確版本的函數
  • 傳遞給實際執行的函數:找到函數后,this指針會作為參數傳給它,這樣函數才知道它在操作哪個對象

從匯編角度看,虛函數調用大致是這樣的:

; animal->makeSound()的匯編實現(簡化版)
mov ecx, [animal]    ; 獲取this指針
mov eax, [ecx]       ; 從this指針(ecx)加載vptr(虛表指針)
call [eax + 偏移量]   ; 調用虛表中對應的函數

# 這里的偏移量是虛函數在虛表中的位置。

這就是為什么letAnimalSpeak(&dog)能正確調用Dog::makeSound()——因為 this 指針指向的是 Dog 對象,所以系統能找到 Dog 的虛函數表,進而調用 Dog 的 makeSound()方法。

this指針讓多態成為可能,它確保了同樣的代碼能根據對象的實際類型執行不同的操作。

2. this指針與const成員函數

在 const 成員函數中,this指針的類型會發生變化:

class Data {
private:
    int value;
public:
    int getValue() const {
        // 在const成員函數中,this的類型是 const Data* const
        // this = new Data(); // 錯誤!不能修改this指針
        // this->value = 10;  // 錯誤!不能通過this修改成員
        return value;
    }
    
    void setValue(int v) {
        // 在非const成員函數中,this的類型是 Data* const
        // this = new Data(); // 錯誤!不能修改this指針
        this->value = v;     // 正確,可以修改成員
    }
};

從編譯器角度看,const成員函數相當于:

// 編譯器內部轉換
int getValue(const Data* const this);  // const成員函數
void setValue(Data* const this, int v);  // 非const成員函數

注意: this 本身總是一個常量指針(const指針),但在 const 成員函數中,它還指向常量對象。

3. this指針與移動語義

在 C++11 引入的移動語義中,this指針同樣發揮著重要作用:

class Resource {
private:
    int* data;
    size_t size;
    
public:
    // 移動構造函數
    Resource(Resource&& other) noexcept {
        // 竊取other的資源
        this->data = other.data;
        this->size = other.size;
        
        // 使other處于有效但未定義狀態
        other.data = nullptr;
        other.size = 0;
    }
    
    // 移動賦值運算符
    Resource& operator=(Resource&& other) noexcept {
        if (this != &other) {  // 自賦值檢查
            delete[] data;     // 釋放自身資源
            
            // 竊取other的資源
            this->data = other.data;
            this->size = other.size;
            
            // 使other處于有效但未定義狀態
            other.data = nullptr;
            other.size = 0;
        }
        return *this;  // 返回自身引用,支持鏈式賦值
    }
};

在移動語義中,this指針用于:

  • 防止自賦值(if (this != &other))
  • 訪問和修改當前對象的成員
  • 返回自身引用(return *this)

八、實戰例子:手動模擬 this 指針的工作方式

為了徹底理解 this 指針,讓我們寫個例子,手動模擬編譯器的工作:

// 常規C++類
class Cat {
private:
    int age;
    string name;
public:
    Cat(int a, string n) : age(a), name(n) {}
    
    void meow() const {
        cout << name << "喵喵,我" << age << "歲了~" << endl;
    }
    
    void setAge(int a) {
        age = a;
    }
};

// 模擬編譯器轉換后的代碼
struct Cat_Raw {
    int age;
    string name;
};

// 注意第一個參數是Cat_Raw*,相當于this指針
void meow_raw(const Cat_Raw* this_ptr) {
    cout << this_ptr->name << "喵喵,我" << this_ptr->age << "歲了~" << endl;
}

void setAge_raw(Cat_Raw* this_ptr, int a) {
    this_ptr->age = a;
}

int main() {
    // 常規C++方式
    Cat cat(3, "小花");
    cat.meow();  // 輸出:小花喵喵,我3歲了~
    cat.setAge(4);
    cat.meow();  // 輸出:小花喵喵,我4歲了~
    
    // 手動模擬編譯器的方式
    Cat_Raw cat_raw{3, "小花"};
    meow_raw(&cat_raw);  // 輸出:小花喵喵,我3歲了~
    setAge_raw(&cat_raw, 4);
    meow_raw(&cat_raw);  // 輸出:小花喵喵,我4歲了~
    
    return0;
}

看到了嗎?兩種方式的輸出完全一樣!這就是 C++ 編譯器在背后做的事情——它把對象方法調用悄悄轉換成了普通函數調用,而this指針就是這個轉換的關鍵。

九、this指針在不同編程語言中的對比

為了幫助大家更好地理解 this 指針,我們來看看它在不同編程語言中的表現:

1. C++中的this

  • 是指向當前對象的常量指針
  • 隱式傳遞給非靜態成員函數
  • 在成員函數內部可以顯式使用,也可以省略
  • 類型為ClassName* const或const ClassName* const(const成員函數)

2. Java中的this

  • 引用當前對象
  • 不能被修改
  • 可以在構造函數中調用其他構造函數:this(args)
  • 也可以用于區分局部變量和成員變量

3. JavaScript中的this

  • 行為更加復雜,由調用方式決定
  • 在全局上下文中,this指向全局對象(瀏覽器中是window)
  • 在函數內部,this取決于函數如何被調用
  • 箭頭函數中的this是詞法作用域的this(繼承自外部上下文)
function test() {
    console.log(this);  // 在瀏覽器中,這里的this是window
}

const obj = {
    name: "對象",
    sayHello: function() {
        console.log(this.name);  // 這里的this是obj
    }
};

obj.sayHello();  // 輸出:對象

const fn = obj.sayHello;
fn();  // 輸出:undefined(因為this變成了全局對象)

這種對比讓我們更加理解 C++ 中 this 指針的特殊性和重要性。

十、this指針的注意事項與陷阱

1. 在靜態成員函數中無法使用

靜態成員函數屬于類而不是對象,所以沒有 this 指針:

class Counter {
private:
    static int totalCount;
    int instanceCount;
    
public:
    static void incrementTotal() {
        totalCount++;
        // instanceCount++;  // 錯誤!靜態方法沒有this指針
        // this->instanceCount++;  // 錯誤!靜態方法沒有this指針
    }
};

2. 在構造函數和析構函數中使用this的注意事項

在構造函數和析構函數中使用 this 時需要格外小心,因為對象可能還未完全構造或已經銷毀:

class Dangerous {
private:
    int* data;
    
public:
    Dangerous() {
        data = newint[100];
        registerCallback(this);  // 危險!對象還未完全構造
    }
    
    ~Dangerous() {
        delete[] data;
        unregisterCallback(this);  // 危險!對象已經銷毀
    }
    
    void callback() {
        // 如果在構造過程中調用,可能訪問未初始化的成員
        // 如果在析構過程中調用,可能訪問已銷毀的成員
    }
};

3. 返回*this的臨時對象問題

使用返回*this的鏈式調用時,需要注意臨時對象的生命周期問題:

class ChainedOps {
public:
    ChainedOps& doSomething() {
        cout << "做點什么..." << endl;
        return *this;
    }
    
    ChainedOps& doSomethingElse() {
        cout << "再做點別的..." << endl;
        return *this;
    }
};

// 安全用法
ChainedOps obj;
obj.doSomething().doSomethingElse();  // 沒問題,obj是持久對象

// 需要注意的用法
ChainedOps().doSomething().doSomethingElse();  // 這行代碼本身沒問題

上面第二種用法,初學者可能會擔心有問題。實際上在這個簡單例子中,根據C++標準,臨時對象的生命周期會延長到整個表達式結束,所以這段代碼能正常工作。

但如果函數返回的是對臨時對象內部數據的引用,就可能有問題:

class Container {
private:
    vector<int> data{1, 2, 3};
    
public:
    vector<int>& getData() {
        return data;  // 返回內部數據的引用
    }
};

// 危險用法
auto& vec = Container().getData();  // 危險!vec引用了臨時Container對象中的data
// 此時臨時Container對象已被銷毀,vec成為懸掛引用
vec.push_back(4);  // 未定義行為!

這里的問題是,臨時對象Container()在表達式結束后被銷毀,但我們保存了它內部數據的引用,這個引用就成了懸掛引用。

所以,關于*this的返回,記住這條簡單規則:

  • 返回*this本身一般是安全的
  • 但如果保存了臨時對象的成員引用,可能導致懸掛引用問題

十一、總結:揭開this指針的神秘面紗

通過今天的深入探索,我們知道了:

  • this指針就是編譯器偷偷塞給成員函數的第一個參數
  • this指針指向調用該成員函數的對象
  • 不同編譯器和平臺對this指針的傳遞方式有所不同
  • this指針讓不同對象能夠共用同一份成員函數代碼
  • this指針在C++的高級特性(如多態、const成員函數、移動語義)中扮演著重要角色
  • 從匯編角度看,this實際上就存儲在特定的寄存器中(如x86的ECX)
  • 使用this指針時需要注意一些陷阱,尤其是在構造/析構函數中以及返回對象引用時

理解 this 指針的工作原理,不僅能讓你寫出更清晰、更強大的 C++ 代碼,還能幫助你更好地理解面向對象編程的本質!

責任編輯:趙寧寧 來源: 跟著小康學編程
相關推薦

2024-12-12 08:41:28

2025-02-19 08:20:00

編程指針C++

2009-01-18 11:45:57

2021-05-06 10:33:30

C++Napiv8

2021-12-16 13:04:41

消息隊列緩存

2018-08-01 14:42:07

團隊職業工作

2011-04-11 11:09:50

this指針

2009-08-27 16:03:31

從c#到c++

2020-09-20 17:50:38

編程語言PythonJava

2018-09-11 17:40:23

容器數據云計算

2021-12-21 15:31:10

C++語言指針

2022-06-20 08:56:25

Kafka微服務運維

2016-02-02 17:10:46

戴爾Equal Logic

2014-01-24 09:49:01

C++指針

2024-05-15 16:01:04

C++編程開發

2009-08-18 11:01:51

2020-11-19 15:21:21

密碼網絡攻擊網絡安全

2010-01-26 13:42:28

C++指針

2022-08-21 14:00:11

消息中間件MQ

2011-04-19 16:38:00

對象指針指針C++
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 99久久精品国产毛片 | 久草视频观看 | 4h影视| 日韩二三区 | 第四色播日韩第一页 | 国产精品久久久久999 | 久久久91精品国产一区二区三区 | caoporn国产精品免费公开 | 亚洲精品一区二区三区在线 | 91麻豆精品国产91久久久更新资源速度超快 | 九九亚洲精品 | 日韩中文字幕在线视频 | 国产精品黄视频 | 久久区二区 | 天天看天天爽 | 色婷婷久久久亚洲一区二区三区 | 久国产视频 | 久久大陆 | 亚洲欧洲成人在线 | 国产色婷婷久久99精品91 | 日本中文字幕一区 | 国产成人高清在线观看 | 免费观看羞羞视频网站 | 免费在线观看黄色av | 欧美不卡视频 | a级在线 | 亚洲男人的天堂网站 | 日本高清中文字幕 | 四虎国产 | 美女久久| 成人免费福利 | 999精彩视频| 久久久91 | 日韩国产专区 | 九色www| 免费视频一区二区 | 91欧美 | 亚洲网址在线观看 | 精品国产免费一区二区三区演员表 | 一级片网站视频 | 国产区第一页 |