翻译《有关编程、重构及其他的终极问题?》——25.不要再用this指针和nullptr比较了

本文探讨了在现代C++编程中,为什么不应再使用this指针与nullptr进行比较,并解释了这种做法可能导致的问题,包括不可预测的行为和编译器优化问题。

翻译《有关编程、重构及其他的终极问题?》——25.不要再用this指针和nullptr比较了

标签(空格分隔): 翻译 技术 C/C++
作者:Andrey Karpov
翻译者:顾笑群 - Rafael Gu
最后更新:2017年05月22日


25.不要再用this指针和nullptr比较了

这段代码摘自CoreCLR项目。PVS-Studio对这段危险的代码诊断如下:V704 ‘this == nullptr’ expression should be avoided - this expression is always false on newer compilers, because ‘this’ pointer can never be NULL(译者注:大意是应该避免“this == nullptr”这种比较,因为“this”指针不可能为NULL,所以这个表达式始终返回false)。

bool FieldSeqNode::IsFirstElemFieldSeq()
{
  if (this == nullptr)
    return false;
  return m_fieldHnd == FieldSeqStore::FirstElemPseudoField;
}

解释
过去大家经常用this指针和0/NULL/nullptr进行比较,在早期的C++发展阶段,这种方式是比较常见的情况,我们已经在“考古”的研究中(译者注:作者把对老代码的研究比喻成考古)常发生这种情况,关于这些,我建议大家可以从这篇关于checking Cfront的文章中读到,因为在那些日子里,this指针的值是可以被改变的,但因为太久远了,我们都忘了这点。

让我们回到用this指针和nullptr比较中。

现在,这种比较是不合法的,因为根据现代的C++标准,this指针是不可能等于nullptr的。

根据正式的C++标准,对于this指针为空指针的方法IsFirstElemFieldSeq()的调用会导致不可预知的行为。

看上去如果this == 0,那么当这个方法被执行的时候,就无法对这个类的字段进行访问了。但在实际中有两个可行的、但不太好的方法可以让这些代码得到执行。根据C++标准,this指针不可能为null,所以编译器可以优化对这个方法的调用,简化成如下的方式:

bool FieldSeqNode::IsFirstElemFieldSeq()
{
  return m_fieldHnd == FieldSeqStore::FirstElemPseudoField;
}

顺便说一下,这里有个陷阱,假设我们有如下的继承结构:

class X: public Y, public FieldSeqNode { .... };
....
X * nullX = NULL;
X->IsFirstElemFieldSeq();

假设Y的类大小为8个字节,然后对于NULL(0x00000000)的原指针,将被矫正为指向FieldSeqNode子对象实例的开始,这样,你就不得不使之偏移sizeof(Y)个字节。这样的话,在IsFirstElemFieldSeq()函数中的this值就被矫正为0x00000008了,那么“this == 0”检查就彻底失去了其意义。

正确的代码
实际上很难给出一个正确代码的示例,只是仅仅从函数中移除判断条件是不够的。你必须进行代码重构来保证从来不会用空指针来调用这个函数。

建议
所以,现在“if (this == nullptr)”虽然过时了,但你还是能在很多应用和库中经常看到类似的代码(MFC库就是一个例子)。这也就是为何Visual C++还是依旧努力的在用this指针和0比较。我猜这是因为编译器开发者还没有疯狂到要把已经正常工作了10多年的代码移除掉。

但规则已经确定了,所以让我们开始避免用this指针和null比较吧。并且,一旦你有空闲时间,检查所有的不合法比较并且重写它们是非常有用的。

编译器在很大程度上应该按照下面的方式工作:开始它们会给出这些比较的警告作为过渡(也许现在已经这么做了,我没有研究过这个问题);然后在某一时刻他们将会全面的支持新标准,这样你的代码(译者注:如果有前述比较的话)就会一起停止工作。所以我强烈建议你开始遵照新的规定,这在以后会很有帮助。

P.S. 当重构时,你也许需要Null object patter

关于这个话题额外的链接:
1. 仍然在使用this指针和Null比较
2. V704诊断

<think>好的,我现在需要回答用户关于C++中this指针的两个问题:1. this指针存在哪里?2. this指针可以为空吗?首先,我需要回忆相关知识,确保信息的准确性。 对于第一个问题this指针的存储位置。根据之前学过的内容,this指针是一个隐含的指针参数,由编译器自动添加到成员函数的参数列表中。当调用成员函数时,对象的地址会被作为this指针传递给函数。所以,this指针应该存在于函数的栈帧中,作为参数传递,而不是存储在对象本身的内存里。需要确认这一点是否正确,可能需要查阅相关资料或书籍。 关于第二个问题this指针是否可以为空。假设有一个对象指针被设置为nullptr,然后调用其成员函数。在这种情况下,this指针就是nullptr,但如果在成员函数中没有访问任何成员变量,可能不会立即崩溃。但这是未定义行为,因为标准规定this指针不能为空。然而,在某些情况下,比如某些编译器优化或特定用法中,可能允许,但这是不安全的。需要明确说明这是未定义行为,即使某些情况下看似可以运行。 接下来,我需要组织语言,确保回答清晰,结构合理。可能分为两个部分,每个问题单独解释,并给出示例代码说明。同时,注意使用中文,并遵循用户之前的格式要求,如行内公式代码块的使用。 需要验证一些细节,比如成员函数调用时this指针的传递机制,以及未定义行为的具体表现。可能需要举例说明当this为空时访问成员变量会导致崩溃,但在不访问的情况下可能不会立即出错,但这仍然是未定义行为,不可依赖。 最后,确保回答准确、易懂,并涵盖用户的问题点。可能需要使用代码示例来演示两种情况,并强调最佳实践,比如避免让this指针为空,确保对象有效。</think>$\textcolor{blue}{一、this指针的存储位置}$ $\textcolor{red}{答案}$:**this指针存储在函数调用栈中**,具体机制如下: 1. $\text{调用原理}$:当调用成员函数时,编译器自动在参数列表最前端插入`this`指针参数 ```cpp // 源代码写法 void MyClass::func(int param) { /*...*/ } // 编译器实际生成的代码(伪代码) void func(MyClass* this, int param) { /*...*/ } ``` 2. $\text{存储位置}$: - 在x86架构中通常通过`ecx`寄存器传递(Windows约定) - 在x86-64架构中通常通过`rdi`寄存器传递(System V ABI) - 寄存器不足时会压入调用栈 3. $\text{对象内存验证}$: ```cpp struct Test { int data; void print_addr() { std::cout << "this: " << this << "\n&data: " << &data << "\n"; } }; // 输出示例: // this: 0x7ffd4d2b1c40 // &data: 0x7ffd4d2b1c40 ``` *数据成员地址与this指针值完全相同,验证this指向对象起始地址* $\textcolor{blue}{二、this指针能否为空}$ $\textcolor{red}{答案}$:**语法上可以存在空this指针,但访问成员会导致未定义行为**,分三种情况: 1. $\text{合法空指针调用}$: ```cpp struct NullTest { void safe_method() { // 不访问任何成员变量 std::cout << "Method called\n"; } }; NullTest* ptr = nullptr; ptr->safe_method(); // 可能正常输出但仍是未定义行为 ``` 2. $\text{危险操作}$: ```cpp struct Danger { int value; void crash() { std::cout << value; // 解引用空this指针 } }; Danger* d = nullptr; d->crash(); // 触发段错误(Segmentation fault) ``` 3. $\text{C++标准规定}$(ISO/IEC 14882:2020 §7.6.1.3): > "在成员函数调用中,表达式`E->F`中的`E`必须是对象的有效指针" $\textcolor{red}{关键实践原则}$: 1. 通过空指针调用成员函数本质是未定义行为(UB) 2. 现代编译器警告示例: ``` warning: 'this' pointer is null [-Wnonnull] ``` 3. 安全模式建议: ```cpp void safe_call() { if(this == nullptr) { // 异常处理或提前返回 throw std::logic_error("Invalid this pointer"); } // 正常操作... } ``` $\textcolor{blue}{三、底层机制验证实验}$ ```cpp #include <iostream> struct AlignmentTest { alignas(16) char buffer[32]; // 强制16字节对齐 void check_this() { std::cout << "this address: " << std::hex << (void*)this << "\nAligned to 16? " << ((uintptr_t)this % 16 == 0) << "\n"; } }; int main() { AlignmentTest obj; obj.check_this(); // 输出合法对齐地址 AlignmentTest* null_obj = nullptr; null_obj->check_this(); // 可能输出0x0但必然崩溃 } ``` $\textcolor{red}{结论}$:理解this指针的存储位置指针风险,是写出健壮C++代码的重要基础。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值