gpt4 book ai didi

c++ - 为什么内联以这种方式内联更改汇编代码?

转载 作者:行者123 更新时间:2023-11-28 01:37:17 24 4
gpt4 key购买 nike

我编写了一个非常简单的C ++程序,以了解“内联”的工作原理:

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

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


我将其编译为汇编代码,而没有使用“内联”。奇怪的是,在两种情况下都生成了一个函数,但它有所不同。如果没有内联,则代码如下所示(删除大多数注释):

_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”指令外,它非常相似。为什么只有当我使用“ inline”时它们才在那里?

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

最佳答案

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()没有特殊的标记,因此假定它是全局的,因此编译器需要提供执行该功能的代码,以供其他人使用,但同时优化程序会看到该功能,它位于同一文件中,因此可以对其进行优化内联以及乐趣1。

fun3()我们指示编译器可以内联该代码,有点暗示它是在此范围内使用的,因此像静态编译器一样,它不会为全局使用生成代码,而是对其进行了优化(内联)

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

也许这里的混乱是内联的意思是内联的意思。不要调用该功能,包括与调用方一致的功能。

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 mov%rsp,%rbp
   4:89 7d fc mov%edi,-0x4(%rbp)
   7:8d 47 07 lea 0x7(%rdi),%eax
   a:5天流行%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


编译后希望内联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,但是llvm / clang就像gnu只是一个前端,如上所示,在中间发生的gnu应该独立于C和C ++表现相同(就自动执行优化或建议的内联而言)。

关于c++ - 为什么内联以这种方式内联更改汇编代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48751426/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com