编译器何时会对指针解引用优化保守,如果有的话?

SSW*_*lks 5 c optimization gcc pointers dereference

所以,我最近对编译器(gcc (GCC) 4.8.3就是那个问题)在优化指针和指针方面的表现感兴趣.

最初我创建了一个简单的整数和一个整数指针,并在其上实现了操作,因此我可以将其打印出来.正如预期的那样,硬编码的所有操作都通过解除引用指针进行优化.

call    __main
leaq    .LC0(%rip), %rcx
movl    $1, %edx
call    printf
Run Code Online (Sandbox Code Playgroud)

甚至在创建一个接受int指针的函数后,取消引用并更改它仍然是完全优化的.

call    __main
leaq    .LC0(%rip), %rcx
movl    $-1, %edx
call    printf
Run Code Online (Sandbox Code Playgroud)

现在,当我将指针视为void并通过将其转换为char并取消引用来进行更改时,它实际上仍然完美地选择了(一个'额外'的mov调用,因为我最初将其视为8字节值,然后作为1指针解除引用的字节值)

call    __main
movl    $4, 44(%rsp)
movb    $2, 44(%rsp)
leaq    .LC0(%rip), %rcx
movl    44(%rsp), %eax
leal    1(%rax), %edx
call    printf
Run Code Online (Sandbox Code Playgroud)

所以在我的问题上:

  1. 有关指针解除引用的编译器优化的一致性如何?什么情况下会选择保守?

  2. 如果项目中的所有指针都是使用restrict关键字声明的,那么我是否可以相信它会像"没有使用指针"一样进行优化?

(假设没有volatile案例)

Ps¹:我知道编译器通常做得很好,并且一般来说,担心帮助编译器进行微小优化的程序员是非生产性的(正如许多人在stackoverflow中指出的那样有关优化的问题).然而,我仍然对此事有好奇心.

Ps²:gcc -O3 -S -c main.c用于生成汇编代码的命令

C代码:(根据要求)

1:

#include <stdio.h>

int main (void)
{
    int a = 4;
    int *ap = &a;

    *ap = 0;
    a += 1;

    printf("%d\n", a);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

2:

#include <stdio.h>

void change(int *p) {
    *p -= 2;
}

int main (void)
{
    int a = 4;
    int *ap = &a;

    *ap = 0;
    change(ap);
    a += 1;

    printf("%d\n", a);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

3:

#include <stdio.h>

void change(void *p) {
    *((char*)p) += 2;
}

int main (void)
{
    int a = 4;
    void *ap = (void*) &a;

    *((char*)(ap)) = 0;
    change(ap);
    a += 1;

    printf("%d\n", a);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

arg*_*age 3

LLVM 和 GCC 都发出静态单分配形式代码作为优化分析的一部分。SSA 代码的有用属性之一是精确显示分配的影响流 - 也就是说,它知道哪些分配会导致其他分配,因此可以检测哪些值可以影响所有其他值。

第一个影响链看起来像

a 1 -> 常量(0) -> ap -> a 2

第二个:a 1 -> Constant(0) -> ap -> p -> a 2

第三个与第二个非常相似。(抱歉,这个符号几乎是虚构的,但我希望它能说明我的观点。)

因为证明 a 对 ap 的影响是确定性的相当简单,所以可以随意取消引用“early”并将指令组合成一个(尽管在前两种情况下,这不是最准确的陈述,因为常量覆盖原始引用并让编译器证明原始赋值不会流到代码末尾。

使编译器对解引用更加保守将涉及变得足够复杂以逃避编译器的理解(我认为在静态程序中很困难)或更可能导致编译器在 SSA 过程中调用 phi 函数(用外行人的话来说,导致分配受到多个先前分配的影响)以不确定的方式。

limit 关键字的目的是向编译器提示两个指针不同。如果生成该指针的代码仍然具有不确定的源(例如,如果运行时创建的数据影响了取消引用的指针值的选择),那么这不会限制在运行时取消引用的使用 - 我认为如果序列化的代码可能会发生这种情况指针从外部源发送到程序中?)