将 NULL 指针强制转换为对象并调用其成员函数之一是否有实际好处?

发布于 2024-08-27 04:44:19 字数 783 浏览 14 评论 0原文

好吧,我知道从技术上讲这是未定义的行为,但尽管如此,我在生产代码中不止一次看到过这种情况。如果我错了,请纠正我,但我也听说有些人使用这个“功能”作为当前 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 块中的类成员。只要这些防护到位,它就是合法的代码,对吗?所以问题仍然存在:是否存在一个实际用例,可以从使用这种构造中受益?我特别担心第二种情况,因为第一种情况更多的是语言限制的解决方法。或者是吗?

附言。对 C 风格的转换感到抱歉,不幸的是,如果可以的话,人们仍然更喜欢少打字。

Ok, so I know that technically this is undefined behavior, but nonetheless, I've seen this more than once in production code. And please correct me if I'm wrong, but I've also heard that some people use this "feature" as a somewhat legitimate substitute for a lacking aspect of the current C++ standard, namely, the inability to obtain the address (well, offset really) of a member function. For example, this is out of a popular implementation of a PCRE (Perl-compatible Regular Expression) library:

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

One can debate whether the exploitation of such a language subtlety in a case like this is valid or not, or even necessary, but I've also seen it used like this:

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

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

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

This works just fine! It avoids a null pointer dereference by testing for the existence of this, and it does not try to access class members in the else block. So long as these guards are in place, it's legitimate code, right? So the question remains: Is there a practical use case, where one would benefit from using such a construct? I'm especially concerned about the second case, since the first case is more of a workaround for a language limitation. Or is it?

PS. Sorry about the C-style casts, unfortunately people still prefer to type less if they can.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

少钕鈤記 2024-09-03 04:44:20

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

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

The first case is not calling anything. It's taking the address. That's a defined, permitted, operation. It yields the offset in bytes from the start of the object to the specified field. This is a very, very, common practice, since offsets like this are very commonly needed. Not all objects can be created on the stack, after all.

The second case is reasonably silly. The sensible thing would be to declare that method static.

各空 2024-09-03 04:44:20

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

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

I don't see any benefit of ((Result*)0)->stat(); - it is an ugly hack which will likely break sooner than later. The proper C++ approach would be using a static method Result::stat() .

offsetof() on the other hand is legal, as the offsetof() macro never actually calls a method or accesses a member, but only performs address calculations.

伪装你 2024-09-03 04:44:20

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

你的第二个构造仍然几乎没有任何作用。从设计的角度来看,如果单个函数在访问成员和不访问成员的情况下都可以完成其工作,并且如果它可以然后将代码的静态部分拆分为单独的,静态函数比期望用户创建一个空指针来操作要合理得多。

从安全角度来看,您仅防范了创建无效 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
}

所以实际上,即使它不是未定义的行为,这是毫无价值的行为。

Everybody else has done a good job of reiterating that the behavior is undefined. But lets pretend it wasn't, and that p->member is allowed to behave in a consistent manner under certain circumstances even if p isn't a valid pointer.

Your second construct would still serve almost no purpose. From a design perspective, you've probably done something wrong if a single function can do its job both with and without accessing members, and if it can then splitting the static portion of the code into a separate, static function would be much more reasonable than expecting your users to create a null pointer to operate on.

From a safety perspective, you've only protected against a small portion of the ways an invalid this pointer can be created. There's uninitialized pointers, for starters:

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

There's pointers that have been initialized, but are still invalid:

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

And even if you always initialize your pointers, and absolutely never accidentally reuse free'd memory:

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

So really, even if it weren't undefined behavior, it is worthless behavior.

她比我温柔 2024-09-03 04:44:20

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

这个效果很好!它避免了空值
通过测试指针取消引用
存在这个,并且它不尝试
访问 else 中的类成员
堵塞。只要这些守卫还在
地方,它是合法的代码,对吗?

不,因为虽然它可能在某些 C++ 实现上工作正常,但它在任何符合要求的 C++ 实现上不工作也是完全可以的。

Although libraries targeting specific C++ implementations may do this, that doesn't mean it's "legitimate" generally.

This works just fine! It avoids a null
pointer dereference by testing for the
existence of this, and it does not try
to access class members in the else
block. So long as these guards are in
place, it's legitimate code, right?

No, because although it might work fine on some C++ implementations, it is perfectly okay for it to not work on any conforming C++ implementation.

兲鉂ぱ嘚淚 2024-09-03 04:44:20

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

仅仅因为它不会在一个特定的测试用例中立即崩溃并不意味着它不会给您带来各种麻烦。

Dereferencing a null-pointer is undefined behavior and anything can happen if you do it. Don't do it if you want a program that works.

Just because it doesn't immediately crash in one specific test case doesn't mean that it won't get you into all kinds of trouble.

香草可樂 2024-09-03 04:44:20

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

Undefined behaviour is undefined behaviour. Do this tricks "work" for your particular compiler? well, possibly. will they work for the next iteration of it. or for another compiler? Possibly not. You pays your money and you takes your choice. I can only say that in nearly 25 years of C++ programming I've never felt the need to do any of these things.

梦幻的味道 2024-09-03 04:44:20

关于声明:

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

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

Regarding the statement:

It avoids a null pointer dereference by testing for the existence of this, and it does not try to access class members in the else block. So long as these guards are in place, it's legitimate code, right?

The code is not legitimate. There is no guarantee that the compiler and/or runtime will actually call to the method when the pointer is NULL. The checking in the method is of no help because you can't assume that the method will actually end up being called with a NULL this pointer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文