C 取消引用指针前空检查上的条件移动指令

C 取消引用指针前空检查上的条件移动指令,c,gcc,assembly,conditional,C,Gcc,Assembly,Conditional,我正在进行CSAPP的练习3.61,它需要编写一个非常简单的函数,在尝试取消引用指针之前检查指针是否为空,该函数应该基于条件移动指令而不是跳转。以下是我在网上找到的一个例子: long cond(长*p){ 返回(!p)?0:*p; } 根据权利要求,该函数可以编译成以下程序集: cond: xor eax, eax test rdi, rdi cmovne rax, QWORD PTR [rdi] ret 我正在WSL上的Ubuntu18.04上运行GCC

我正在进行CSAPP的练习3.61,它需要编写一个非常简单的函数,在尝试取消引用指针之前检查指针是否为空,该函数应该基于条件移动指令而不是跳转。以下是我在网上找到的一个例子:

long cond(长*p){
返回(!p)?0:*p;
}
根据权利要求,该函数可以编译成以下程序集:

cond:
    xor eax, eax
    test rdi, rdi
    cmovne rax, QWORD PTR [rdi]
    ret
我正在WSL上的Ubuntu18.04上运行GCC 7.3.0(来自APT包
GCC/bionic更新,现在是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的源操作数是不可能的,对吗


声明已打开,但我认为它无效。

这是一个无分支版本:

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

以下是无分支版本:

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

优化无效,因为cmov从其源操作数执行无条件加载,即使最终未使用,也会出错。编辑的代码无法解决此问题。即使条件为false,也会访问源位置。@iBug是否与任何
cmov
一样?只需要一些C代码?此处:@iBug您更新的代码没有为我生成
cmove
,但它与
-O1
一起生成。还要注意的是,您不需要
z
成为
静态的
。除了fuz和其他人指出的正确性问题之外,即使您可以使用条件移动,也不太可能是简单分支上的优化。为什么?分支预测。只要分支是可预测的,并且空指针检查是可预测的,现代处理器上的分支预测就对您有利。您会发现,英特尔编译器更喜欢在这种情况下只进行测试和分支,即使它可以使用条件移动。这种优化是无效的,因为cmov从其源操作数执行无条件加载,即使它最终没有被使用,也会出错。编辑的代码无法解决此问题。即使条件为false,也会访问源位置。@iBug是否与任何
cmov
一样?只需要一些C代码?此处:@iBug您更新的代码没有为我生成
cmove
,但它与
-O1
一起生成。还要注意的是,您不需要
z
成为
静态的
。除了fuz和其他人指出的正确性问题之外,即使您可以使用条件移动,也不太可能是简单分支上的优化。为什么?分支预测。只要分支是可预测的,并且空指针检查是可预测的,现代处理器上的分支预测就对您有利。您会发现,英特尔的编译器更喜欢在这种情况下进行测试和分支,即使它可以使用条件移动。