空指针也能调用成员函数?

C++空指针调用

一、问题简介

昨晚,微信公众号推送了网易的笔试题让小伙伴们参考,看了第一道题,博主就觉得“嗯,我果然还是见识短哈!”为什么呢,我们先看看这个代码~

class cal
{
public:
	void show()
	{
		cout << "cal::Show()" << endl;
	}

};
int main()
{
	cal *pcal = NULL;
	pcal->show();
	getchar();
	return 0;
}
Q;该程序输出的结果什么?
博主毫不犹豫的选择了“运行时崩溃”这个选项
然而。。。。真正的答案却是这个指针可以正确的调用show()函数。。what?!why?!这不科学
怀着对科学严谨的态度(我要吐了)哭,博主还是去别的大大的博客转了一圈。诶!?还真被我找到了,这到底是为什么呢?

我们现在再看一段代码!

class cal  
{  
public:  
    cal() :a(10)  
    {}  
    void display()  
    {  
        cout << a << endl;  
    }  
private:  
    int a;  
};  
int main()  
{  
    cal *pcal = NULL;  
    pcal->display();  
    getchar();  
    return 0;  
}  

和上面的那段代码很像吧,但是呢,这段代码在运行的时候是会崩溃的,为什么呢!?
仔细的你发现了没,上面第一段代码里,指针调用的函数没有用到this指针,而这一段里用到了,所以关键就在于你是否用到了this指针。
这是因为,我们在编写一个类的时候,它的所有的对象共用了成员函数,所以在编译期间我们就已经确定了成员函数的地址,而类又依靠this指针来区分各个不同的对象以便于找到正确的数据。对于第一个例子,它的this指针就等于NULL而show()是一个静态类型的成员函数,在调用时不会传this指针给它,所以我们可以顺利的调用它,但是第二个例子中我们用到了类的数据a在调用时被编译器解释为this->a ,但是你得this是NULL啊,你指什么啊!?
所以,你明白了吗。。。
二、总结





<think> 我们正在讨论C++中空对象指针调用函数的问。根据用户提供的引用材料,我们可以总结出以下关键点: 1. 成员函数在内存中的存储方式:成员函数不属于单个对象,而是属于类本身。所有对象共享同一个函数体,函数地址在编译时确定。因此,调用成员函数本质上是调用一个全局函数,该函数的第一个参数是对象的this指针。 2. 空指针调用成员函数的机制:当使用空指针调用成员函数时,编译器会将其转换为一个全局函数调用,形式为:`ClassName::MemberFunction(nullptr)`。如果该函数内部不访问任何成员变量(即不使用this指针),那么程序不会崩溃,因为函数体不依赖于对象的状态。反之,如果函数内部使用了this指针(例如访问成员变量),则会导致未定义行为(通常是段错误)。 3. 虚函数的特殊情况:虚函数通过虚函数表(vtable)调用,而虚函数表位于对象的内存空间中。因此,用空指针调用函数时,需要访问对象的虚函数表指针(通常位于对象起始位置),这会导致访问空指针,从而引发崩溃。 现在,我们回答用户的问:C++空对象指针为什么能调用函数? ### C++空对象指针能调用函数的原因 在C++中,空对象指针(如`Person* p = nullptr;`)能够调用成员函数,这是由于C++成员函数的底层实现机制决定的。具体原因如下: 1. **成员函数的存储独立于对象**: 类的成员函数在内存中只有一份副本,由该类的所有对象共享。成员函数的地址在编译期间就已确定,与具体的对象实例无关[^2][^4]。因此,即使对象指针为空,函数地址仍然是有效的。 2. **函数调用的转换机制**: 当通过对象指针调用成员函数(如`p->Print()`)时,编译器会将其转换为一个全局函数调用形式。例如: ```cpp // 原始调用 p->Print(); // 编译器转换为 Person::Print(p); // 将p作为this指针传入 ``` 这里的`p`就是传入函数的`this`指针。当`p`为空时,`this`指针即为`nullptr`。如果函数内部没有使用`this`指针(例如不访问成员变量),那么函数依然可以正常执行[^1][^4]。 3. **空指针调用的风险分析**: - **安全情况**:成员函数不访问任何成员变量或虚函数时,调用不会引发错误。例如: ```cpp void Print() { cout << "I'm LiHua." << endl; } // 安全 ``` 因为这个函数只输出常量字符串,不依赖对象状态[^1][^4]。 - **危险情况**:如果函数内部访问了成员变量(如`this->fd`)或调用函数,则会引发未定义行为(通常是程序崩溃)。例如: ```cpp void getfd() { cout << fd << endl; } // 访问this->fd,危险 ``` 此时,由于`this`指针为空,访问成员变量相当于访问`nullptr`的内存地址,导致段错误[^1][^4]。 #### 代码示例说明 参考以下代码: ```cpp class Person { public: void Print() { cout << "I'm LiHua." << endl; } // 未使用this指针 void getfd() { cout << fd << endl; } // 使用了this->fd int fd = 0; }; void test01() { Person* p = nullptr; p->Print(); // 正常执行(安全) p->getfd(); // 崩溃(危险:访问this->fd) } ``` - `p->Print()` 成功执行,因为函数体不依赖`this`指针。 - `p->getfd()` 导致崩溃,因为它试图通过空`this`指针访问成员变量`fd`[^1][^4]。 #### 虚函数调用的特殊性 虚函数调用(如`p->func2()`)需要访问对象的虚函数表(vtable),而虚函数表指针位于对象内存的起始位置。当对象指针为空时,访问虚函数表相当于解引用空指针,必然导致崩溃[^2][^3]。例如: ```cpp class A { public: virtual void func2() {} // 虚函数 }; int main() { A* p = nullptr; p->func2(); // 崩溃:需要访问虚函数表 } ``` #### 如何保证安全? 在成员函数内部添加空指针检查可提高健壮性: ```cpp void getfd() { if (this == nullptr) return; // 安全检查 cout << fd << endl; } ``` 但更推荐的做法是避免在可能为空的指针上调用成员函数[^1][^4]。 ### 总结 C++允许空指针调用成员函数,是因为成员函数调用不依赖于对象的内存状态(函数地址编译期确定),但函数内部若使用`this`指针则会导致未定义行为。虚函数调用因涉及虚表访问,空指针调用必然崩溃[^1][^2][^3][^4]。 §§相关问§§ 1. 空指针调用成员函数时,虚函数的行为有何不同? 2. 如何在C++中安全地处理空指针调用以避免程序崩溃? 3. 空指针调用成员函数在实际代码中的应用场景有哪些? </think> ### C++空对象指针能调用函数的原因 在C++中,空对象指针(如 `Person* p = nullptr;`)能够调用成员函数,这源于C++的底层实现机制和成员函数的存储方式。以下是详细解释: #### 1. **成员函数的存储机制** - C++类的成员函数**不属于单个对象**,而是**属于类本身**。所有对象共享同一个函数体实例。 - 成员函数的地址在**编译期间就已确定**,存储在代码区(而非对象内存中)。 - 调用过程本质上是:`ClassName::FunctionName(this, ...)`,其中 `this` 指针作为隐式参数传递[^2][^4]。 $$ \text{调用 } p \rightarrow \text{func}() \quad \Rightarrow \quad \text{实际执行 } \text{ClassName::Func}(p) $$ #### 2. **调用过程解析** 当通过空指针调用函数时: ```cpp Person* p = nullptr; p->Print(); // 等价于 Person::Print(nullptr) ``` - 编译器将调用转换为全局函数形式,传入 `this = nullptr`[^1][^4] - 若函数**不访问成员变量**(不依赖 `this` 指针),代码能正常执行 - 若函数**访问成员变量**(如 `this->fd`),则解引用空指针导致崩溃[^1][^4] #### 3. **安全调用 vs 危险调用** ```cpp class Person { public: // 安全:不依赖this指针 void SafeFunc() { cout << "Hello" << endl; } // 危险:访问成员变量(依赖this) void DangerFunc() { cout << fd << endl; } int fd = 0; }; ``` - `p->SafeFunc()` ✅ 成功执行(仅使用编译期确定的代码地址) - `p->DangerFunc()` ❌ 崩溃(尝试访问 `nullptr->fd`)[^1] #### 4. **虚函数的特殊情况** 虚函数调用需要访问对象的**虚函数表指针**(位于对象内存起始处): ```cpp p->virtualFunc(); // 需要访问 p->__vptr ``` - 即使不访问成员变量,虚函数调用也会崩溃 - 因为虚函数表查找需解引用 `this` 指针[^2][^3] #### 5. **技术本质总结** | 特性 | 说明 | |---------------------|----------------------------------------------------------------------| | **函数存储位置** | 代码区(编译期固定地址) | | **调用机制** | 转换为全局函数调用 `Class::Func(this)` | | **空指针风险** | 取决于函数是否访问 `this` 指针指向的内存 | | **编译器处理** | 不检查指针有效性(遵循C++"信任程序员"哲学)[^4] | #### 代码示例分析 ```cpp void test() { Person* p = nullptr; p->SafeFunc(); // 输出 "Hello"(成功) p->DangerFunc(); // 崩溃(访问 nullptr->fd) } ``` - 安全调用不崩溃:因实际执行的是独立函数 `Person::SafeFunc(nullptr)`[^4] - 危险调用崩溃:因执行 `cout << (nullptr->fd)` 导致解引用空指针[^1] > **关键结论**:空指针调用函数是因成员函数地址编译期确定且独立于对象内存,但若函数内部使用 `this` 指针则会导致未定义行为[^2][^4]。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值