9

好的,所以我知道从技术上讲这是未定义的行为,但尽管如此,我在生产代码中不止一次看到过这种情况。如果我错了,请纠正我,但我也听说有些人使用这个“功能”作为当前 C++ 标准缺乏方面的某种合法替代品,即无法获取地址(嗯,成员函数的偏移量。例如,这是 PCRE(Perl 兼容的正则表达式)库的流行实现:

#ifndef offsetof
#define offsetof(p_type,field) ((size_t)&(((p_type *)0)->field))
#endif

人们可以争论在这样的情况下利用这种语言的微妙之处是否有效,甚至是必要的,但我也看到它是这样使用的:

struct Result
{
   void stat()
   {
      if(this)
         // do something...
      else
         // do something else...
   }
};

// ...somewhere else in the code...

((Result*)0)->stat();

这工作得很好!它通过测试 的存在来避免空指针取消引用this,并且它不会尝试访问else块中的类成员。只要这些防护措施到位,它就是合法的代码,对吧?所以问题仍然存在:是否有一个实际的用例,可以从使用这种结构中受益?我特别关心第二种情况,因为第一种情况更多的是一种解决语言限制的方法。或者是吗?

PS。对 C 风格的演员表感到抱歉,不幸的是,如果可以的话,人们仍然喜欢少打字。

4

7 回答 7

9

第一种情况是不调用任何东西。它正在获取地址。这是一个定义的、允许的操作。它产生从对象开始到指定字段的字节偏移量。这是一种非常非常常见的做法,因为像这样的偏移量是非常常见的。毕竟,并非所有对象都可以在堆栈上创建。

第二种情况相当愚蠢。明智的做法是将该方法声明为静态的。

于 2010-04-02T22:56:00.323 回答
4

我没有看到任何好处((Result*)0)->stat();- 这是一个丑陋的黑客,可能迟早会崩溃。正确的 C++ 方法是使用静态方法Result::stat()

另一方面,offsetof() 是合法的,因为 offsetof() 宏从不实际调用方法或访问成员,而只执行地址计算。

于 2010-04-02T23:05:31.483 回答
4

其他人都很好地重申了行为是未定义的。但是让我们假装它不是,p->member即使p它不是一个有效的指针,它也可以在某些情况下以一致的方式表现。

您的第二个构造仍然几乎没有任何作用。从设计的角度来看,如果单个函数可以在访问成员和不访问成员的情况下完成其工作,并且如果它可以将代码的静态部分拆分为单独的静态函数,那么您可能做错了而不是期望您的用户创建一个空指针来操作。

从安全的角度来看,您只保护了一小部分this可以创建无效指针的方法。对于初学者来说,有未初始化的指针:

Result* p;
p->stat(); //Oops, 'this' is some random value

有些指针已经初始化,但仍然无效:

Result* p = new Result;
delete p;
p->stat(); //'this' points to "safe" memory, but the data doesn't belong to you

即使您总是初始化指针,并且绝对不会意外重用已释放的内存:

struct Struct {
    int i;
    Result r;
}
int main() {
    ((Struct*)0)->r.stat(); //'this' is likely sizeof(int), not 0
}

所以真的,即使它不是未定义的行为,它也是毫无价值的行为。

于 2010-04-03T07:33:50.097 回答
3

尽管针对特定 C++ 实现的库可能会这样做,但这并不意味着它通常是“合法的”。

这工作得很好!它通过测试 this 的存在来避免空指针取消引用,并且它不会尝试访问 else 块中的类成员。只要这些防护措施到位,它就是合法的代码,对吧?

不,因为尽管它在某些 C++ 实现上可能工作得很好,但它不能在任何符合 C++ 的实现上工作是完全可以的。

于 2010-04-02T22:59:51.627 回答
3

取消引用空指针是未定义的行为,如果您这样做,任何事情都可能发生。如果您想要一个有效的程序,请不要这样做。

仅仅因为它不会在一个特定的测试用例中立即崩溃并不意味着它不会让你陷入各种麻烦。

于 2010-04-02T23:00:20.393 回答
2

未定义的行为是未定义的行为。这个技巧对您的特定编译器“有效”吗?好吧,可能。他们会为它的下一次迭代工作吗?或另一个编译器?可能不是。你付你的钱,你做出你的选择。我只能说,在近 25 年的 C++ 编程生涯中,我从未觉得有必要做这些事情。

于 2010-04-02T22:59:27.683 回答
1

关于声明:

它通过测试 this 的存在来避免空指针取消引用,并且它不会尝试访问 else 块中的类成员。只要这些防护措施到位,它就是合法的代码,对吧?

代码不合法​​。当指针为 NULL 时,不能保证编译器和/或运行时会实际调用该方法。检查方法没有任何帮助,因为您不能假设该方法实际上最终会被NULL this指针调用。

于 2010-04-02T23:07:56.980 回答