2

假设我有以下代码:

int f() {
  int foo = 0;
  int bar = 0;

  foo++;
  bar++;

  // many more repeated operations in actual code
  foo++;
  bar++;

  return foo+bar;
}

将重复的代码抽象为一个单独的函数,我们得到

static void change_locals(int *foo_p, int *bar_p) {
  *foo_p++;
  *bar_p++;
}

int f() {
  int foo = 0;
  int bar = 0;

  change_locals(&foo, &bar);
  change_locals(&foo, &bar);

  return foo+bar;
}

我希望编译器能够内联change_locals函数,并将*(&foo)++生成的代码中的内容优化为foo++.

如果我没记错的话,获取局部变量的地址通常会阻止一些优化(例如,它不能存储在寄存器中),但是当没有对地址进行指针运算并且它没有从函数中转义时,这是否适用?使用更大的change_locals,如果它被声明inline__inline在 MSVC 中)会有所不同吗?

我对 GCC 和 MSVC 编译器的行为特别感兴趣。

4

3 回答 3

3

inline(及其所有表亲_inline__inline...)被 gcc 忽略。它可能会内联任何它认为是优势的东西,但在较低的优化级别除外。

x86 的 gcc -O3 的代码过程是:

        .text
        .p2align 4,,15
.globl f
        .type   f, @function
f:
        pushl   %ebp
        xorl    %eax, %eax
        movl    %esp, %ebp
        popl    %ebp
        ret
        .ident  "GCC: (GNU) 4.4.4 20100630 (Red Hat 4.4.4-10)"

它返回零,因为 *ptr++ 没有按照你的想法做。将增量更正为:

    (*foo_p)++;
    (*bar_p)++;

结果是

        .text
        .p2align 4,,15
.globl f
        .type   f, @function
f:
        pushl   %ebp
        movl    $4, %eax
        movl    %esp, %ebp
        popl    %ebp
        ret

所以它直接返回 4。它不仅内联了它们,而且优化了计算。

来自 vs 2005 的 Vc++ 提供了类似的代码,但它也为change_locals(). 我使用了命令行

/O2 /FD /EHsc /MD /FA /c /TP
于 2011-04-05T05:59:37.813 回答
2

如果我没记错的话,获取局部变量的地址通常会阻止一些优化(例如,它不能存储在寄存器中),但是当没有对地址进行指针运算并且它没有从函数中转义时,这是否适用?

一般的答案是,如果编译器可以确保没有其他人会更改其背后的值,则可以安全地将其放入寄存器中。

把这想象成编译器首先执行内联,然后将所有这些*&foo(由内联产生)转换为简单的foo,然后再决定是否应该将它们放在堆栈上的内存中的寄存器中。

使用较大的change_locals,如果将其声明为内联(MSVC 中的__inline)会有所不同吗?

同样,一般来说,编译器是否决定内联某些东西是使用启发式完成的。如果您明确指定要内联某些内容,编译器可能会将其纳入其决策过程。

于 2011-04-05T06:32:01.187 回答
1

我已经使用这个测试了 gcc 4.5、MSC 和 IntelC:

#include <stdio.h>

void change_locals(int *foo_p, int *bar_p) {
  (*foo_p)++;
  (*bar_p)++;
}

int main() {
  int foo = printf("");
  int bar = printf("");

  change_locals(&foo, &bar);
  change_locals(&foo, &bar);

  printf( "%i\n", foo+bar );
}

他们都内联/优化了 foo+bar 值,但也确实为 change_locals() 生成了代码(但没有使用它)。

不幸的是,仍然不能保证他们会为任何类型的“本地函数”做同样的事情。

海湾合作委员会:

__Z13change_localsPiS_:
    pushl   %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %edx
    movl    12(%ebp), %eax
    incl    (%edx)
    incl    (%eax)
    leave
    ret

_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    pushl   %ebx
    subl    $28, %esp
    call    ___main
    movl    $LC0, (%esp)
    call    _printf
    movl    %eax, %ebx
    movl    $LC0, (%esp)
    call    _printf
    leal    4(%ebx,%eax), %eax
    movl    %eax, 4(%esp)
    movl    $LC1, (%esp)
    call    _printf
    xorl    %eax, %eax
    addl    $28, %esp
    popl    %ebx
    leave
    ret
于 2011-04-05T05:57:55.660 回答