【问题标题】:What is the function of this statement *(long*)0=0;?这个语句*(long*)0=0;的作用是什么?
【发布时间】:2014-02-18 00:57:58
【问题描述】:

在下面的代码中,*(long*)0=0;if 子句一起使用,但它的用途是什么?

if(r.wid*r.ht < tot)
    *(long*)0=0;

【问题讨论】:

  • 该语句应该是一个具有描述性名称的宏。 (这是宏的少数好理由之一。您希望崩溃发生在正确的函数中,因此堆栈帧是有意义的)
  • C++ 还是 C?我没有看到任何 C++ 功能,但标准可能不同。
  • 你真的试过这个吗?上次我这样做时,我的编译器只是简单地将整个语句排除在外。根据 C 标准完全合法。
  • 现代编译器可能会删除整段代码。 then 部分是未定义的行为,因此编译器可以假定它无法访问。假设if 表达式没有副作用,则可以删除整个if 语句。
  • Redis 使用类似的技巧来模拟 seg 错误,然后您可以在调试器中从那里获取,请参阅What does “((char)-1) = 'x';” code mean?。正如我在对链接问题的回答中所说,这是未定义的行为,如果在某些情况下对其进行了优化,我不会感到惊讶。上一个线程What is the simplest standard conform way to produce a Segfault in C? 也很合适。

标签: c++ c


【解决方案1】:

它将 0 写入 0,解释为 long 的地址,即 NULL 指针。这不是一件有效的事情,因为NULL 绝不是您可以有效地拥有程序可以访问的数据的地址。此代码触发未定义的行为;一般来说,你不能依赖它来产生任何特定的效果。

但是,通常使用这样的代码来强制segmentation fault-type 崩溃,有时将其放入调试器很方便。

同样,这是未定义的行为;不能保证它会导致这样的错误,但是在有分段错误的系统上,上面的代码很可能会产生一个。在其他系统上,它可能会做一些完全不同的事情。

如果您遇到段错误,有时以这种方式触发比在调试器中手动设置断点更方便。例如,如果您不使用 IDE,通常更容易将这几个标记键入到所需位置的代码中,而不是向调试器提供(文本)命令,指定确切的源代码文件和行号手动可能有点烦人。

【讨论】:

  • 为什么保证这段代码会导致分段错误?
  • 有理由支持这个而不是assert吗?
  • @Zaibis C99 草案说“如果已为指针分配了无效值,则一元 * 运算符的行为未定义”,并澄清这一点以包括 NULL 指针。
  • 虽然@unwind 的回答对于大多数通用机器来说是完全正确的(他正确地指出这是未定义的行为,并且不能保证),但这样的代码确实在嵌入式系统中找到了一席之地,那里确实是地址 0(或空指针映射到的任何地址)处的东西(内存映射 IO,或专用内存地址)。
  • @Damon 在我倾向于处理的庞大代码库中,我们倾向于在发布版本中保留尽可能多的断言启用,因为实际上不可能当然你不能通过一些代码路径来击中它们,尽管质量保证和测试开发尽了最大努力,但可能还是错过了。
【解决方案2】:

在教科书 C 中,abort 是故意使程序崩溃的方法。但是,当您在金属附近进行编程时,您可能不得不担心abort 可能无法按预期工作! abort 的标准 POSIXy 实现调用 getpidkill(通过 raise)将 SIGABRT 传递给进程,这反过来可能导致信号处理程序的执行,它可以随心所欲。有一些情况,例如在malloc 的深处,为了应对灾难性的、可能是对抗性的内存损坏,您需要在完全不触及堆栈的情况下强制崩溃(特别是,不执行返回指令,这可能会跳转到恶意代码)。 *(long *)0 = 0 在这种情况下并不是最疯狂的尝试。它仍然有执行信号处理程序的风险,但这是不可避免的;如果不进行函数调用,就无法触发SIGKILL。更严重的是(恕我直言)现代编译器有点太可能看到这一点,观察它具有未定义的行为,删除它,并删除测试,因为测试不可能是真的,因为没有人会故意调用未定义的行为,不是吗?如果这种逻辑看起来有悖常理,请阅读 LLVM 组的discourse on undefined behavior and optimizationpart 2part 3)。

有更好的方法来实现这一目标。现在的许多编译器都有一个内在的(例如 gcc,clang:__builtin_trap()),它生成一个机器指令,保证会导致硬件故障并传递SIGILL;与带有指针的未定义技巧不同,编译器不会对其进行优化。如果你的编译器没有这个,但有汇编插入,你可以手动插入这样的指令——这可能是足够低级的代码,额外的机器依赖性不是什么大问题。或者,您可以致电_exit。这可以说是最安全的播放方式,因为它不会冒着运行信号处理程序的风险,而且即使在内部也不涉及函数返回。但这确实意味着您不会得到核心转储。

【讨论】:

  • 要完全标准,请参阅my common on another answer。使用*(long *)0 = 0 崩溃的标准方式实际上是*(long *)(void *)0 = 0
  • @Shahbaz 抱歉,不,中间 void * 没有任何区别。您在两个方面犯了错误:首先,0 本身就是一个有效的空指针常量,因此 (long *)0(long *)(void *)0both 空指针。其次,无论您如何构造空指针,取消引用空指针都会引发未定义的行为。
  • 0 是一个有效的 NULL 指针,如果它没有被强制转换为任何指针类型。否则,标准不必将演员单挑出来void *。让我们假设一个虚构的编译器,其中 NULL 的值为 0xFFFF。如果编译器看到 0 分配给一个指针,它将使用 0xFFFF 代替,因为那是 NULL 指针。如果它看到(void *)0,它也会使用0xFFFF,因为那是空指针。但它应该在(long *)0 中使用0x0000 的确切值,因为这不是 NULL 指针。
  • @Shahbaz 我知道你从哪里得到这种印象,但不,这不是语言的工作方式。将值为零的整型常量表达式(它不必是字面意义上的0)转换为 any 指针类型,通过 any 意味着,总是产生空指针该类型(从技术上讲,每种类型都可以使用不同的位表示)。我在这台电脑上没有标准,所以我不能引用它,但如果你有一个副本,请仔细阅读转换表达式语义的描述。 (1/2)
  • @Shahbaz 标准同意 Zack:“值为 0 的整数常量表达式,这样的表达式转换为 void * 类型,称为空指针常量。 "然后(parens mine):“如果空指针常量(0,如上所述)转换为指针类型(long*),则生成的指针称为空指针......” - N1570 §6.3。 2.3
【解决方案3】:

要使程序“异常退出”,请使用abort() 函数 (http://pubs.opengroup.org/onlinepubs/9699919799/functions/abort.html)。

“如果条件 X 不成立,则使程序异常退出”的标准 C/C++ 习语是 assert() 宏。上面的代码会更好写:

assert( !(r.wid*r.ht < tot) );

或者(如果您乐于忽略边缘情况),它读起来更清晰:

assert( r.wid*r.ht >= tot );

【讨论】:

    【解决方案4】:

    如果 r 的宽度乘以高度小于总和,则使程序崩溃。

    【讨论】:

    • 这是一个答案;它只是没有你想要的有用的答案。但这没有理由说它没有答案。
    • 这是程序的缩进,但不一定是它实际做的。
    • OP 询问目的是什么,知道这一点的唯一方法是猜测原作者的意图是什么 - 我认为这个答案很好地总结了这一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-24
    • 2015-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多