在取消引用指针之前对 NULL 检查的条件移动指令

标签 c gcc assembly conditional-statements

我正在做 CSAPP 的练习 3.61,它需要编写一个非常简单的函数来检查指针是否为 NULL,然后再尝试取消引用它,这应该基于条件移动指令而不是比跳跃。这是我在网上找到的一个例子:

long cond(long* p) {
    return (!p) ? 0 : *p;
}

根据claims,该函数可以编译成如下程序集:

cond:
    xor eax, eax
    test rdi, rdi
    cmovne rax, QWORD PTR [rdi]
    ret

我在 WSL 上的 Ubuntu 18.04 上运行 GCC 7.3.0(来自 APT 包 gcc/bionic-updates,now 4:7.3.0-3ubuntu2.1 amd64)。该计算机在 Intel Coffee Lake(即第 8 代 Core-i)处理器上运行。

我尝试了以下命令:

gcc -S a.c -O3
gcc -S a.c -O3 -march=x86-64
gcc -S a.c -O3 -march=core2
gcc -S a.c -O3 -march=k8

老实说,我没能在生成的 a.s 文件中观察到任何差异,因为它们看起来都像

cond:
    xorl    %eax, %eax
    testq   %rdi, %rdi
    je      .L1
    movq    (%rdi), %rax
.L1:
    ret

有没有可能有这样一个函数,可以编译成条件移动,而不需要跳转?


编辑:正如评论中所述,CMOVxx 系列指令无条件加载操作数,只有实际赋值操作是有条件的,因此没有运气放置 *p(或(%rdi))作为CMOV的源操作数,对吗?

claim 是在this page但我认为这是无效的。

最佳答案

这是一个无分支的版本:

inline long* select(long* p, long* q) {
    uintptr_t a = (uintptr_t)p;
    uintptr_t b = (uintptr_t)q;
    uintptr_t c = !a;
    uintptr_t r = (a & (c - 1)) | (b & (!c - 1));
    return (long*)r;
}

long cond(long* p) {
    long t = 0;
    return *select(p, &t);
}

gcc-8.2 的程序集:

cond(long*):
        mov     QWORD PTR [rsp-8], 0
        xor     eax, eax
        test    rdi, rdi
        sete    al
        lea     rdx, [rax-1]
        neg     rax
        and     rdi, rdx
        lea     rdx, [rsp-8]
        and     rax, rdx
        or      rdi, rax
        mov     rax, QWORD PTR [rdi]
        ret

clang-7 的汇编:

cond(long*):                              # @cond(long*)
        mov     qword ptr [rsp - 8], 0
        xor     eax, eax
        test    rdi, rdi
        lea     rcx, [rsp - 8]
        cmovne  rcx, rax
        or      rcx, rdi
        mov     rax, qword ptr [rcx]
        ret

关于在取消引用指针之前对 NULL 检查的条件移动指令,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54558152/

相关文章:

assembly - 设置视频模式或清屏后文本模式光标消失

assembly - 16 位端口地址的 32 位分配

c - HMAC-SHA1 的 Objective-C 示例代码

c++ - Mat 和 setMouseCallback 函数

c - 我如何将 "tell"告诉 C 编译器,代码不应该被优化掉?

c - 了解 MIPS 汇编代码段

c - 从 XCode 中的 C 程序到 SWI-Prolog 的接口(interface)

c - 仅对数组中的奇数或偶数进行排序

c++ - 如何让 cmake 使用 "-pthread"而不是 -lpthread”?

用汇编语言编写的c调用函数