编译器指令剖析
前言
日常工作中,经常会遇到一些类似于__attribute__((xxx))
样式的“代码”,这些“代码”的作用一般都是告诉编译器,在编译阶段需要干点额外事情来达到我们的要求。这里,姑且将这些“代码”称为编译器指令。本文主要记录一些编译器指令的实现原理以及具体用途。
正文
__ attribute__((noreturn))
告诉编译器,被该指令修饰的函数不会返回,以便编译器可以通过删除那些不会执行的代码来进行优化。
比如下面这段代码👇
void __attribute__((noreturn)) playing(void) {
do {
// do something
} while (1);
// code will never be executed
}
2
3
4
5
6
while(1)
之后的代码永远不会执行,即该函数不会返回,则可以通过该编译器指令进行优化。
那么问题来了,编译器究竟删除了哪些代码?是如何优化的?
由于没有找到详细介绍的官方资料,所以只好从汇编层面下手。
以下使用的汇编指令是 Xcode 模拟器生成,所以格式上和 iOS 真机不太一样。具体可以参考这里。
- 先来看下不加这条编译指令时,函数编译后的汇编代码:
// 源码
void playing(void) {
do {
// do something
} while (1);
// code will never be executed
}
2
3
4
5
6
7
; 汇编
attribute.app`playing:
0x104825f80 <+0>: pushq %rbp ; 将caller帧指针保存在栈里
0x104825f81 <+1>: movq %rsp, %rbp ; 将帧指针指向栈顶,准备开始一个新的栈帧
0x104825f84 <+4>: jmp 0x104825f89 ; <+9> at ViewController.m:35:5
-> 0x104825f89 <+9>: jmp 0x104825f8e ; <+14> at ViewController.m:35:5
0x104825f8e <+14>: movb $0x1, %al
0x104825f90 <+16>: testb $0x1, %al
0x104825f92 <+18>: jne 0x104825f89 ; <+9> at ViewController.m:35:5
0x104825f98 <+24>: jmp 0x104825f9d ; <+29> at ViewController.m:37:1
0x104825f9d <+29>: popq %rbp ; 将caller帧指针从栈里取出
0x104825f9e <+30>: retq ; 返回
2
3
4
5
6
7
8
9
10
11
12
popq
和 retq
这两条指令配合使用,便可以完成一个子函数的调用返回。详见这里。
- 再看下加上这条编译指令后,函数编译后的汇编代码:
// 源码
void __attribute__((noreturn)) playing(void) {
do {
// do something
} while (1);
// code will never be executed
}
2
3
4
5
6
7
; 汇编
attribute.app`playing:
0x106e72f80 <+0>: pushq %rbp ; 将caller帧指针保存在栈里
0x106e72f81 <+1>: movq %rsp, %rbp ; 将帧指针指向栈顶,准备开始一个新的栈帧
0x106e72f84 <+4>: jmp 0x106e72f89 ; <+9> at ViewController.m:35:5
-> 0x106e72f89 <+9>: jmp 0x106e72f8e ; <+14> at ViewController.m:35:5
0x106e72f8e <+14>: movb $0x1, %al
0x106e72f90 <+16>: testb $0x1, %al
0x106e72f92 <+18>: jne 0x106e72f89 ; <+9> at ViewController.m:35:5
0x106e72f98 <+24>: jmp 0x106e72f9d ; <+29> at ViewController.m:37:1
0x106e72f9d <+29>: ud2 ; 注意这里,是一条中断指令
2
3
4
5
6
7
8
9
10
11
通过对比两次汇编代码,可以看到函数被__attribute__((noreturn))
修饰后,尾部的 pop
和 ret
两条指令会被优化掉,取而代之的是一条 ud2 指令。既然函数被标记了__attribute__((noreturn))
,就表明程序不会返回;若返回了,便会序执行到 ud2 这里,引发 EXC_BAD_INSTRUCTION
。
以上,分析的是 C 函数,而对于 Objective-C 中的类方法和实例方法,经过尝试,该编译器指令并不会生效。至于原因,据Clang文档中描述,编译器可能会诊断被 noreturn
修饰的函数,从而能够使函数仍然可以返回给调用方。
参考资料:
https://clang.llvm.org/docs/AttributeReference.html http://www.keil.com/support/man/docs/armcc/armcc_chr1359124965789.htm