c语言关掉编译优化,C 语言编译器优化导致进程行为完全不同的一个简单例子

这篇博客探讨了C语言代码中递归函数与死循环的现象。通过clang编译器在不同优化级别下生成的汇编代码,展示了未优化和优化后代码的区别。未优化的代码会导致栈溢出,而优化后的代码则简化为无限循环。编译器通过分析函数f的递归行为,推断出其无外部副作用并移除了调用,揭示了编译器如何进行优化和推测代码逻辑。
摘要由CSDN通过智能技术生成

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

看这段简单但不完全平凡的代码:1

2

3

4

5

6

7

8

9

10

11// Filename: 0.cvoid f(int n) {

f(++n);

}

int main() {

int i = 0;

for(;;++i) {

f(i*i);

}

}

如果用 clang 0.c 编译,进程会很快因为 f 这个函数无限递归导致栈溢出而终止。

如果用 clang -O3 0.c 编译,则进程会一直停留在死循环里,不会终止。

未经优化得到的汇编代码是1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56.section__TEXT,__text,regular,pure_instructions

.macosx_version_min 10, 11

.globl_f

.align4, 0x90

_f: ## @f

.cfi_startproc

## BB#0:

pushq%rbp

Ltmp0:

.cfi_def_cfa_offset 16

Ltmp1:

.cfi_offset %rbp, -16

movq%rsp, %rbp

Ltmp2:

.cfi_def_cfa_register %rbp

subq$16, %rsp

movl%edi, -4(%rbp)

movl-4(%rbp), %edi

addl$1, %edi

movl%edi, -4(%rbp)

callq_f

addq$16, %rsp

popq%rbp

retq

.cfi_endproc

.globl_main

.align4, 0x90

_main: ## @main

.cfi_startproc

## BB#0:

pushq%rbp

Ltmp3:

.cfi_def_cfa_offset 16

Ltmp4:

.cfi_offset %rbp, -16

movq%rsp, %rbp

Ltmp5:

.cfi_def_cfa_register %rbp

subq$16, %rsp

movl$0, -4(%rbp)

movl$0, -8(%rbp)

LBB1_1: ## =>This Inner Loop Header: Depth=1

movl-8(%rbp), %eax

imull-8(%rbp), %eax

movl%eax, %edi

callq_f

## BB#2: ## in Loop: Header=BB1_1 Depth=1

movl-8(%rbp), %eax

addl$1, %eax

movl%eax, -8(%rbp)

jmpLBB1_1

.cfi_endproc

.subsections_via_symbols

O3 优化后对应的汇编代码是1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39.section__TEXT,__text,regular,pure_instructions

.macosx_version_min 10, 11

.globl_f

.align4, 0x90

_f: ## @f

.cfi_startproc

## BB#0:

pushq%rbp

Ltmp0:

.cfi_def_cfa_offset 16

Ltmp1:

.cfi_offset %rbp, -16

movq%rsp, %rbp

Ltmp2:

.cfi_def_cfa_register %rbp

popq%rbp

retq

.cfi_endproc

.globl_main

.align4, 0x90

_main: ## @main

.cfi_startproc

## BB#0:

pushq%rbp

Ltmp3:

.cfi_def_cfa_offset 16

Ltmp4:

.cfi_offset %rbp, -16

movq%rsp, %rbp

Ltmp5:

.cfi_def_cfa_register %rbp

.align4, 0x90

LBB1_1: ## =>This Inner Loop Header: Depth=1

jmpLBB1_1

.cfi_endproc

.subsections_via_symbols

可见优化的版本里最后执行的是一个简单的死循环 (34-35 行),对函数 f 的调用被完全移除了,尽管还是“假模假式”地定义了 f (什么功能都没有,也没有递归调用的部分)。

至于 gcc (clang) 是怎么推断出 f 这个函数虽然在做事,但做的事是“封闭的”、对外界没有“逻辑上的”副作用 (尽管有事实上的副作用,那就是栈溢出),可以在这里看到一些端倪。想了想,也许是通过把递归转换为循环可以看出 f 这个函数没有副作用,所以就移除了。

那个页面提到 LLVM 会为了方便后续分析而把1for (i = 7; i*i < 1000; ++i)

变成1for (i = 0; i != 25; ++i)

真没想到它会做这样的变换。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值