【问题标题】:Why is inline changing the assembly code in this way?为什么内联以这种方式更改汇编代码?
【发布时间】:2018-07-22 21:58:00
【问题描述】:

我写了一个非常简单的 C++ 程序来理解“内联”是如何工作的:

inline int square(int x) {
    return x*x;
}

int main() {
    int y = square(1234);
    return y;
}

我将它编译成汇编代码,不带和带“内联”。奇怪的是,在这两种情况下都生成了一个函数,但它是不同的。没有内联代码看起来像这样(删除大多数 cmets):

_Z6squarei:                             # @_Z6squarei
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -4(%rbp)
    movl    -4(%rbp), %edi
    imull   -4(%rbp), %edi
    movl    %edi, %eax
    popq    %rbp
    retq
.Lfunc_end0:

main:                                   # @main
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp
    movl    $1234, %edi             # imm = 0x4D2
    movl    $0, -4(%rbp)
    callq   _Z6squarei
    movl    %eax, -8(%rbp)
    movl    -8(%rbp), %eax
    addq    $16, %rsp
    popq    %rbp
    retq
.Lfunc_end1:

使用内联,它看起来像这样:

main:                                   # @main
    .cfi_startproc
    pushq   %rbp
.Lcfi0:
    .cfi_def_cfa_offset 16
.Lcfi1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
.Lcfi2:
    .cfi_def_cfa_register %rbp
    subq    $16, %rsp
    movl    $1234, %edi             # imm = 0x4D2
    movl    $0, -4(%rbp)
    callq   _Z6squarei
    movl    %eax, -8(%rbp)
    movl    -8(%rbp), %eax
    addq    $16, %rsp
    popq    %rbp
    retq
.Lfunc_end0:

_Z6squarei:                             # @_Z6squarei
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -4(%rbp)
    movl    -4(%rbp), %edi
    imull   -4(%rbp), %edi
    movl    %edi, %eax
    popq    %rbp
    retq
.Lfunc_end1:

它非常相似,除了新的“cfi”指令。为什么只有当我使用“内联”时它们才会出现?

还有第二个问题:有没有办法告诉编译器真正使这个函数内联? (我用的是clang++-5.0)。

【问题讨论】:

  • 您正在使用哪些优化标志?如果您将优化设置为零或调试,编译器将不会内联任何内容,因为它使设置断点变得困难。尝试使用-O2-Os 并再次检查生成的程序集。
  • inline 只是对编译器的提示。优化器决定根据许多事情内联您的函数,inline 关键字可能会或可能不会被考虑在内。如果您在关闭优化的情况下进行编译(如在您的程序集列表中),则优化器不会运行并且实际上没有函数被内联。不要那样做。此外,如果没有优化,生成的代码可能会相当奇怪和毫无意义。这是因为您禁止编译器优化这些怪癖。
  • 有没有办法告诉编译器真正使这个函数内联?不,内联只是对编译器的请求而不是命令。
  • @achal:确实inline 只是一个提示,但这并不排除另一种更强大的方式。 __attribute__(always_inline) 但这里不需要强制内联,只需启用优化即可。
  • 关键字inline 不代表它以前的意思。现在它只是意味着如果 LINKER 出现了来自不同编译单元的相同函数的定义,它就是选择一个,而不是引发one definition rule 错误。它不再与编译器所做的事情有任何关系。任何给定的编译器都可以随心所欲地扩展函数,只要它当然遵循as if 规则。

标签: c++ assembly inline


【解决方案1】:
unsigned int fun0 ( unsigned int );

static unsigned int fun1 ( unsigned int x )
{
    return(x+1);
}

unsigned int fun2 ( unsigned int x )
{
    return(x+2);
}

inline unsigned int fun3 ( unsigned int x )
{
    return(x+3);
}

unsigned int hello ( unsigned int x )
{
    unsigned int y;
    y=fun0(x);
    y=fun1(y);
    y=fun2(y);
    y=fun3(y);
    return(y);
}

故意使用不同的指令集:

Disassembly of section .text:

00000000 <fun2>:
   0:   e2800002    add r0, r0, #2
   4:   e12fff1e    bx  lr

00000008 <hello>:
   8:   e92d4010    push    {r4, lr}
   c:   ebfffffe    bl  0 <fun0>
  10:   e8bd4010    pop {r4, lr}
  14:   e2800006    add r0, r0, #6
  18:   e12fff1e

fun0() 是外部的,编译器在那里没有可见性,它必须设置调用并获取返回值。

fun1() 被标记为静态,因此我们已经表明我们希望该函数是该对象/文件/范围的本地函数,因此编译器没有理由在那里创建一个函数供其他人远程访问,优化器可以看到它在同一个文件中的函数所以选择内联它。

fun2() 没有特殊标记,它被假定为全局的,因此编译器需要提供执行该功能的代码以供其他人可能使用,但同时优化器看到该功能,它在同一个文件中,所以将其优化为 inline 和 fun1。

fun3() 我们表示编译器可以内联这个,有点暗示它是在这个范围内消费的,所以像静态编译器没有生成代码供全局消费,并优化(内联)

功能上,hello 将 x 发送到 fun0(),然后将其转换为 y。然后我们将 1+2+3 = 6 添加到它。因此,要内联 fun1、fun2、fun3,您只需将 6 添加到 fun0() 的输出中。这就是我们看到的 fun1() fun2() 和 fun3() 是内联的。

也许这里的混淆是 inline 的意思是 inline 的意思。不要调用函数包含与调用者一致的功能。

unsigned int fun2 ( unsigned int x )
{
    return(x+2);
}

unsigned int hello ( unsigned int x )
{
    return(fun2(x));
}

使用我正在使用的工具,我实际上不需要要求它内联

00000000 <fun2>:
   0:   e2800002    add r0, r0, #2
   4:   e12fff1e    bx  lr

00000008 <hello>:
   8:   e2800002    add r0, r0, #2
   c:   e12fff1e    bx  lr

优化器还是这样做了,它没有设置对 fun2 的调用,而是采用了 fun2 的功能,即向操作数添加 2,并且它只是在 hello IN LINE 中执行此操作。

使用您的工具,请注意全局函数是以任何一种方式创建的,但是当您要求它内联时,它看起来并没有实际执行任何操作,请检查反汇编和汇编,反汇编通常更容易阅读,更少混淆。

注意,使用我的第一个示例和 C++ 编译器,所以我没有得到“嘿,你没有使用 C++ 编译器”:

0000000000000000 <_Z4fun2j>:
   0:   8d 47 02                lea    0x2(%rdi),%eax
   3:   c3                      retq   
   4:   66 90                   xchg   %ax,%ax
   6:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
   d:   00 00 00 

0000000000000010 <_Z5helloj>:
  10:   48 83 ec 08             sub    $0x8,%rsp
  14:   e8 00 00 00 00          callq  19 <_Z5helloj+0x9>
  19:   48 83 c4 08             add    $0x8,%rsp
  1d:   83 c0 06                add    $0x6,%eax
  20:   c3                      retq   

同样的故事,内联和静态并没有产生供其他人使用的全局函数。并且编译器生成了对外部函数的调用,然后添加了 6。

注意没有优化:

00000000 <fun1>:
   0:   e52db004    push    {r11}       ; (str r11, [sp, #-4]!)
   4:   e28db000    add r11, sp, #0
   8:   e24dd00c    sub sp, sp, #12
   c:   e50b0008    str r0, [r11, #-8]
  10:   e51b3008    ldr r3, [r11, #-8]
  14:   e2833001    add r3, r3, #1
  18:   e1a00003    mov r0, r3
  1c:   e28bd000    add sp, r11, #0
  20:   e49db004    pop {r11}       ; (ldr r11, [sp], #4)
  24:   e12fff1e    bx  lr

00000028 <fun2>:
  28:   e52db004    push    {r11}       ; (str r11, [sp, #-4]!)
  2c:   e28db000    add r11, sp, #0
  30:   e24dd00c    sub sp, sp, #12
  34:   e50b0008    str r0, [r11, #-8]
  38:   e51b3008    ldr r3, [r11, #-8]
  3c:   e2833002    add r3, r3, #2
  40:   e1a00003    mov r0, r3
  44:   e28bd000    add sp, r11, #0
  48:   e49db004    pop {r11}       ; (ldr r11, [sp], #4)
  4c:   e12fff1e    bx  lr

00000050 <hello>:
  50:   e92d4800    push    {r11, lr}
  54:   e28db004    add r11, sp, #4
  58:   e24dd010    sub sp, sp, #16
  5c:   e50b0010    str r0, [r11, #-16]
  60:   e51b0010    ldr r0, [r11, #-16]
  64:   ebfffffe    bl  0 <fun0>
  68:   e50b0008    str r0, [r11, #-8]
  6c:   e51b0008    ldr r0, [r11, #-8]
  70:   ebffffe2    bl  0 <fun1>
  74:   e50b0008    str r0, [r11, #-8]
  78:   e51b0008    ldr r0, [r11, #-8]
  7c:   ebfffffe    bl  28 <fun2>
  80:   e50b0008    str r0, [r11, #-8]
  84:   e51b0008    ldr r0, [r11, #-8]
  88:   ebfffffe    bl  0 <fun3>
  8c:   e50b0008    str r0, [r11, #-8]
  90:   e51b3008    ldr r3, [r11, #-8]
  94:   e1a00003    mov r0, r3
  98:   e24bd004    sub sp, r11, #4
  9c:   e8bd4800    pop {r11, lr}
  a0:   e12fff1e    bx  lr

称它们都没有内联...您在测试中使用了什么优化?如果你尝试优化呢? (llvm/clang 为您提供了超过 gnu 的多种优化机会)

使用 llvm 和优化进行编辑。

两个独立的文件

unsigned int fun0 ( unsigned int x )
{
    return(x+7);
}

还有这个

unsigned int fun0 ( unsigned int );

inline unsigned int fun3 ( unsigned int x )
{
    return(x+3);
}

unsigned int hello ( unsigned int x )
{
    unsigned int y;
    y=fun0(x);
    y=fun3(y);
    return(y);
}

无需优化即可构建

0000000000000000 : 0: 55 推送 %rbp 1: 48 89 e5 移动 %rsp,%rbp 4: 89 7d fc mov %edi,-0x4(%rbp) 7: 8d 47 07 lea 0x7(%rdi),%eax a: 5d 流行 %rbp b: c3 retq

0000000000000000 <hello>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
   8:   89 7d fc                mov    %edi,-0x4(%rbp)
   b:   e8 00 00 00 00          callq  10 <hello+0x10>
  10:   89 45 f8                mov    %eax,-0x8(%rbp)
  13:   89 c7                   mov    %eax,%edi
  15:   e8 00 00 00 00          callq  1a <hello+0x1a>
  1a:   89 45 f8                mov    %eax,-0x8(%rbp)
  1d:   48 83 c4 10             add    $0x10,%rsp
  21:   5d                      pop    %rbp
  22:   c3                      retq   

post compile 希望 fun0 被内联,哦,它确实优化了你好

0000000000000000 <fun0>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   8d 47 07                lea    0x7(%rdi),%eax
   7:   5d                      pop    %rbp
   8:   c3                      retq   
   9:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)

0000000000000010 <hello>:
  10:   55                      push   %rbp
  11:   48 89 e5                mov    %rsp,%rbp
  14:   83 c7 07                add    $0x7,%edi
  17:   e8 00 00 00 00          callq  1c <hello+0xc>
  1c:   5d                      pop    %rbp
  1d:   c3                      retq   

经过优化编译。

0000000000000000 <fun0>:
   0:   8d 47 07                lea    0x7(%rdi),%eax
   3:   c3                      retq   

0000000000000000 <hello>:
   0:   50                      push   %rax
   1:   e8 00 00 00 00          callq  6 <hello+0x6>
   6:   83 c0 03                add    $0x3,%eax
   9:   59                      pop    %rcx
   a:   c3                      retq   

clang 为您提供不同的优化机会。

好的,知道了,随着文件数量的增加,llvm 工具的优化组合几乎呈指数级增长,对于更大的项目,我发现如果你编译未优化它会给后面的优化器更多的内容,但这当然取决于在许多因素上,不幸的是,这些组合变得惊人。如果我先用优化编译,然后再组合和优化,我会得到我想要的。

0000000000000000 <fun0>:
   0:   8d 47 07                lea    0x7(%rdi),%eax
   3:   c3                      retq   

0000000000000010 <hello>:
  10:   8d 47 0a                lea    0xa(%rdi),%eax
  13:   c3                      retq   

fun3 加了 3 fun0 加了 7,对 fun0 的调用是内联的,我最终从两个文件中得到一个外部函数一个内部内联,只需添加 10。

我在这里使用了 C,但是像 gnu 这样的 llvm/clang 只是一个前端,如上所示,gnu 在中间发生的事情应该表现得与 C 和 C++ 无关(就进行自动或建议内联的优化而言)。

【讨论】:

    猜你喜欢
    • 2012-06-29
    • 1970-01-01
    • 2021-11-11
    • 1970-01-01
    • 1970-01-01
    • 2019-09-04
    • 2011-01-06
    • 2011-11-04
    • 2010-11-19
    相关资源
    最近更新 更多