为啥这个不允许编译器执行的示例会导致使用 cmov 取消引用空指针?
Posted
技术标签:
【中文标题】为啥这个不允许编译器执行的示例会导致使用 cmov 取消引用空指针?【英文标题】:Why does this example of what compilers aren't allowed to do cause null pointer dereferencing using cmov?为什么这个不允许编译器执行的示例会导致使用 cmov 取消引用空指针? 【发布时间】:2015-11-27 19:18:47 【问题描述】:C 代码:
int cread(int *xp)
return (xp ? *xp : 0);
汇编代码:(来自一个教科书示例,说明编译器不允许做什么)使用条件移动指令
movl $0, %eax
testl %edx, %edx
cmovne (%edx), %eax
这是 Computer Systems: A Programmer's Perspective(第 2 版)中使用的示例,用于说明如果条件的任一分支导致错误,则无法使用条件数据传输编译代码。在这种情况下,错误将是 xp 的空指针解引用。
我知道 xp 被取消引用,但我不明白 xp 是如何变成空指针的。这不取决于将指针作为参数传递给函数吗?
【问题讨论】:
是的,只有NULL
作为参数传入。但代码的重点是检查NULL
,在这种情况下返回零。 cmov
版本无法做到这一点,因为它总是会尝试取消引用指针,所以它会出错而不是返回零。
这个问题有些误导。我不认为显示的汇编代码实际上是由 gcc 生成的。如果是,那就是编译器错误。
@WumpusQ.Wumbley:你是对的。书中的汇编代码实际上将Invalid implementation of function cread xp in register %edx
作为无效示例代码的前言(OP 向我们展示的部分)。它不是由 gcc 生成的,正如这个 OP 所暗示的那样。
在我看来它匹配。因为movl 0,eax
而不是 xoring eax ?
cmov[cc] (mem), %reg
不是安腾式的推测性负载。 无条件地执行加载,仅预测目标寄存器的值。
【参考方案1】:
汇编代码在技术上是有效的,但如果输入为 NULL
并且与 C 代码的行为不匹配,则会出错。鉴于事情的重点是在这种情况下返回零而不是错误,这是错误的。 C 等效项是:
int cread(int *xp)
int val = *xp;
return (xp ? val : 0);
如您所见,它首先取消引用xp
,然后才检查xp
是否为NULL
,因此这显然不适用于NULL
输入。
【讨论】:
我认为代码是有效的。 CSAPP 3e 更改了这部分代码。 有趣但不足为奇的是,GCC 和 ICC 都将您展示的 C 代码编译成一个简单的mov eax, [rdi]
。
相关:Hard to debug SEGV due to skipped cmov from out-of-bounds memory 详细解释了 cmov
是一个始终需要两个操作数的 ALU 选择操作,这与 ARM 谓词加载或 Itanium 推测加载不同。【参考方案2】:
如果您拨打电话
cread(0);
cmovene
指令会出现段错误,因为它会评估 *xp
,即使该值永远不会被使用。
在汇编语言中,这由(%edx)
表示。 IE。无论edx
的值如何,都会加载%edx
地址处的内存内容。
cmov
的值已被普遍质疑。例如Linus Torvalds 不是粉丝。
【讨论】:
【参考方案3】:我知道 xp 被取消引用,但我不明白 xp 是如何变成空指针的。这不取决于将指针作为参数传递给函数吗?
您在技术上是正确的(而教科书在技术上是错误的——理论上,在某些情况下,编译器可以合法地生成该代码)。
但是;可以生成该代码的情况是:
a) 编译器(和/或链接器)可以证明没有调用者将 NULL 传递给函数。在这种情况下,编译器还证明cmov
是没有意义的,可以用普通存储替换(mov
之前没有任何测试)。
b) 编译器(和/或链接器)知道在程序集中引用 NULL(这不是 C 语言并且不需要遵循 C 语言的规则)是可以的。通常 C 中的 NULL 是汇编中的地址 0x00000000,并且通常故意使地址 0x000000 处的区域无法访问以帮助捕获错误;但是操作系统或程序没有理由无法使 0x0000000 处的区域可访问(例如,通常只使用链接器脚本就可以做到这一点)。
【讨论】:
以上是关于为啥这个不允许编译器执行的示例会导致使用 cmov 取消引用空指针?的主要内容,如果未能解决你的问题,请参考以下文章
C++ 唯一指针;为啥这个示例代码会出现编译错误?错误代码太长了,我无法指定