为什么这些函数调用没有优化?

AnA*_*ons 8 c optimization assembly gcc clang

我已经尝试用Clang和GCC编译这段代码:

struct s { int _[50]; };

void (*pF)(const struct s), (*pF1)(struct s), (*pF2)(struct s *);

main()
{
    struct s a;

    pF2(&a);

    pF(a), pF1(a);
}
Run Code Online (Sandbox Code Playgroud)

结果是一样的.虽然pF不允许调用来修改其唯一的参数,但是a为第二次调用复制了该对象pF1.为什么是这样?

这是装配输出(来自GCC):

; main

        push    rbx
        sub rsp, 0D0h
        mov rbx, rsp
        mov rdi, rsp
        call    cs:pF2

    ;create argument for pF1 call (as there the argument is modified)
    ;and copy the local a into it
    ;although it seems not needed because the local isn't futher read anyway

        sub rsp, 0D0h
        mov rsi, rbx
        mov ecx, 19h
        mov rdi, rsp
    ;

        rep movsq
        call    cs:pF

    ;copy the local a into the argument created once again
    ;though the argument cannot be modified by the function pointed by pF

        mov rdi, rsp
        mov rsi, rbx
        mov ecx, 19h
        rep movsq
    ;

        call    cs:pF1
        add rsp, 1A0h
        xor eax, eax
        pop rbx
        retn
Run Code Online (Sandbox Code Playgroud)

优化器无法看到,因为指向的函数pF无法修改其参数(因为它已声明const),因此省略了上次复制操作?另外,最近我看到,由于a代码中没有进一步读取变量,它可以将其存储用于函数参数.

相同的代码可以写成:

; main

            push    rbx
            sub rsp, 0D0h

            mov rdi, rsp
            call    cs:pF2

            call    cs:pF

            call    cs:pF1
            add rsp, 0D0h
            xor eax, eax
            pop rbx
            retn
Run Code Online (Sandbox Code Playgroud)

我正在用-O3旗帜编译.我错过了什么吗?

即使我没有调用UB(默认情况下是函数指针NULL),它也是一样的,而我将它们初始化为:

#include <stdio.h>

struct s { int _[50]; };

extern void f2(struct s *a);

void (*pF)(const struct s), (*pF1)(struct s), (*pF2)(struct s *) = f2;

extern void f1(struct s a)
{
    a._[2] = 90;
}

extern void f(const struct s a)
{
    for(size_t i = 0; i < sizeof(a._)/sizeof(a._[0]); ++i)
        printf("%d\n", a._[i]);
}

extern void f2(struct s *a)
{
    a->_[6] = 90;

    pF1 = f1, pF = f;
}
Run Code Online (Sandbox Code Playgroud)

gsg*_*gsg 3

我不认为这种优化是合法的。您忽略的是带有 const 参数的函数类型与带有非常量参数的函数类型兼容,因此改变其参数的函数可能会被分配给指针pF

这是一个示例程序:

struct s {
    int x;
};

/* Black hole so that DCE doesn't eat everything */
void observe(void *);

void (*pF)(const struct s);

void test(struct s arg) {
    arg.x = 0;
    observe(&arg);
}

void assignment(void) {
    pF = test;
}
Run Code Online (Sandbox Code Playgroud)

最重要的是,参数的 const 注释无法为编译器提供有关参数存储是否被被调用者改变的可靠信息。执行此优化似乎要求 ABI 要求参数存储不被改变(或某种整体程序分析,但不用介意)。

  • Peter:问题仍然是一样的——函数的实现可能会改变存储。最重要的是,参数的“const”注释没有给编译器提供可靠的信息。 (2认同)