[iOS开发]编译过程
Posted Billy Miracle
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[iOS开发]编译过程相关的知识,希望对你有一定的参考价值。
参考链接:https://juejin.cn/post/6844903742785978376
前言
ios 开发中使用的是编译语言,所谓编译语言是在执行的时候,必须先通过编译器生成机器码,机器码可以直接在CPU上执行,所以执行效率较高,是使用 Clang / LLVM 来编译的。LLVM是一个模块化和可重用的编译器和工具链技术的集合,Clang 是 LLVM 的子项目,是 C,C++ 和 Objective-C 编译器,目的是提供惊人的快速编译。下面我们来看看编译过程,总的来说编译过程分为几个阶段:预处理 -> 词法分析 -> 语法分析 -> 静态分析 -> 生成中间代码和优化 -> 汇编 -> 链接
具体过程
命令行查看编译的过程:
clang -ccc-print-phases main.m
结果:
(一)预处理
对下面代码进行预处理
#import "AppDelegate.h"
#define NUMBER 1
int main(int argc, char * argv[])
NSString * appDelegateClassName;
@autoreleasepool
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
NSLog(@"%d", NUMBER);
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
使用终端到main.m
所在文件夹,使用命令:clang -E main.m -F /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks
,结果如下:
# 9 "./AppDelegate.h" 2
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@end
# 9 "main.m" 2
int main(int argc, char * argv[])
NSString * appDelegateClassName;
@autoreleasepool
appDelegateClassName = NSStringFromClass([AppDelegate class]);
NSLog(@"%d", 1);
return UIApplicationMain(argc, argv, ((void *)0), appDelegateClassName);
这一步编译器所做的处理是:
- 宏替换(在源码中使用的宏定义会被替换为对应
#define
的内容)
建议大家不要在需要预处理的代码中加入内联代码逻辑。 - 头文件引入(
#include
,#import
)
使用对应文件.h的内容替换这一行的内容,所以尽量减少头文件中的#import
,使用@class替代,把#import
放到.m文件中。 - 处理条件编译指令 (
#if
,#else
,#endif
)
(二)词法解析
使用clang -Xclang -dump-tokens main.m
词法分析,结果如下:
at '@' [StartOfLine] Loc=<./AppDelegate.h:10:1>
identifier 'interface' Loc=<./AppDelegate.h:10:2>
identifier 'AppDelegate' [LeadingSpace] Loc=<./AppDelegate.h:10:12>
colon ':' [LeadingSpace] Loc=<./AppDelegate.h:10:24>
identifier 'UIResponder' [LeadingSpace] Loc=<./AppDelegate.h:10:26>
less '<' [LeadingSpace] Loc=<./AppDelegate.h:10:38>
identifier 'UIApplicationDelegate' Loc=<./AppDelegate.h:10:39>
greater '>' Loc=<./AppDelegate.h:10:60>
at '@' [StartOfLine] Loc=<./AppDelegate.h:13:1>
identifier 'end' Loc=<./AppDelegate.h:13:2>
int 'int' [StartOfLine] Loc=<main.m:11:1>
identifier 'main' [LeadingSpace] Loc=<main.m:11:5>
l_paren '(' Loc=<main.m:11:9>
int 'int' Loc=<main.m:11:10>
identifier 'argc' [LeadingSpace] Loc=<main.m:11:14>
comma ',' Loc=<main.m:11:18>
char 'char' [LeadingSpace] Loc=<main.m:11:20>
star '*' [LeadingSpace] Loc=<main.m:11:25>
identifier 'argv' [LeadingSpace] Loc=<main.m:11:27>
l_square '[' Loc=<main.m:11:31>
r_square ']' Loc=<main.m:11:32>
r_paren ')' Loc=<main.m:11:33>
l_brace '' [LeadingSpace] Loc=<main.m:11:35>
identifier 'NSString' [StartOfLine] [LeadingSpace] Loc=<main.m:12:5>
star '*' [LeadingSpace] Loc=<main.m:12:14>
identifier 'appDelegateClassName' [LeadingSpace] Loc=<main.m:12:16>
semi ';' Loc=<main.m:12:36>
at '@' [StartOfLine] [LeadingSpace] Loc=<main.m:13:5>
identifier 'autoreleasepool' Loc=<main.m:13:6>
l_brace '' [LeadingSpace] Loc=<main.m:13:22>
identifier 'appDelegateClassName' [StartOfLine] [LeadingSpace] Loc=<main.m:15:9>
equal '=' [LeadingSpace] Loc=<main.m:15:30>
identifier 'NSStringFromClass' [LeadingSpace] Loc=<main.m:15:32>
l_paren '(' Loc=<main.m:15:49>
l_square '[' Loc=<main.m:15:50>
identifier 'AppDelegate' Loc=<main.m:15:51>
identifier 'class' [LeadingSpace] Loc=<main.m:15:63>
r_square ']' Loc=<main.m:15:68>
r_paren ')' Loc=<main.m:15:69>
semi ';' Loc=<main.m:15:70>
identifier 'NSLog' [StartOfLine] [LeadingSpace] Loc=<main.m:17:9>
l_paren '(' Loc=<main.m:17:14>
at '@' Loc=<main.m:17:15>
string_literal '"%d"' Loc=<main.m:17:16>
comma ',' Loc=<main.m:17:20>
numeric_constant '1' [LeadingSpace] Loc=<main.m:17:22 <Spelling=main.m:9:16>>
r_paren ')' Loc=<main.m:17:28>
semi ';' Loc=<main.m:17:29>
r_brace '' [StartOfLine] [LeadingSpace] Loc=<main.m:18:5>
return 'return' [StartOfLine] [LeadingSpace] Loc=<main.m:19:5>
identifier 'UIApplicationMain' [LeadingSpace] Loc=<main.m:19:12>
l_paren '(' Loc=<main.m:19:29>
identifier 'argc' Loc=<main.m:19:30>
comma ',' Loc=<main.m:19:34>
identifier 'argv' [LeadingSpace] Loc=<main.m:19:36>
comma ',' Loc=<main.m:19:40>
identifier 'nil' [LeadingSpace] Loc=<main.m:19:42>
comma ',' Loc=<main.m:19:45>
identifier 'appDelegateClassName' [LeadingSpace] Loc=<main.m:19:47>
r_paren ')' Loc=<main.m:19:67>
semi ';' Loc=<main.m:19:68>
r_brace '' [StartOfLine] Loc=<main.m:20:1>
eof '' Loc=<main.m:20:2>
这一步把源文件中的代码转化为特殊的标记流,源码被分割成一个一个的字符和单词,在行尾Loc中都标记出了源码所在的对应源文件和具体行数,方便在报错时定位问题。Clang定义的所有Token类型。 可以分为下面这4类:
- 关键字:语法中的关键字,比如 if、else、while、for 等;
- 标识符:变量名;
- 字面量:值、数字、字符串;
- 特殊符号:加减乘除等符号。
(三)语法分析
执行 clang 命令 clang -Xclang -ast-dump -fsyntax-only main.m
,主要得到如下结果:
|-ObjCInterfaceDecl 0x11f8b46e8 <./AppDelegate.h:10:1, line:13:2> line:10:12 AppDelegate
|-FunctionDecl 0x11f8b4a60 <main.m:11:1, line:20:1> line:11:5 main 'int (int, char **)'
| |-ParmVarDecl 0x11f8b4810 <col:10, col:14> col:14 used argc 'int'
| |-ParmVarDecl 0x11f8b4940 <col:20, col:32> col:27 used argv 'char **':'char **'
| `-CompoundStmt 0x11f8e0188 <col:35, line:20:1>
| `-ObjCAutoreleasePoolStmt 0x11f8e0038 <line:13:5, line:18:5>
| `-CompoundStmt 0x11f8e0020 <line:13:22, line:18:5>
| `-CallExpr 0x11f8dffd8 <line:17:9, col:28> 'void'
| |-ImplicitCastExpr 0x11f8dffc0 <col:9> 'void (*)(id, ...)' <FunctionToPointerDecay>
| | `-DeclRefExpr 0x11f8dfdb8 <col:9> 'void (id, ...)' Function 0x11f8dfc18 'NSLog' 'void (id, ...)'
| |-ImplicitCastExpr 0x11f8e0008 <col:15, col:16> 'id':'id' <BitCast>
| | `-ObjCStringLiteral 0x11f8dff18 <col:15, col:16> 'NSString *'
| | `-StringLiteral 0x11f8dfe18 <col:16> 'char [3]' lvalue "%d"
| `-IntegerLiteral 0x11f8dff38 <line:9:16> 'int' 1
`-FunctionDecl 0x11f8dfc18 <line:17:9> col:9 implicit used NSLog 'void (id, ...)' extern
|-ParmVarDecl 0x11f8dfd10 <<invalid sloc>> <invalid sloc> 'id':'id'
|-BuiltinAttr 0x11f8dfcb8 <<invalid sloc>> Implicit 883
`-FormatAttr 0x11f8dfd80 <col:9> Implicit NSString 1 2
这一步是把词法分析生成的标记流,解析成一个抽象语法树(abstract syntax tree -- AST
),同样地,在这里面每一节点也都标记了其在源码中的位置。
(四)静态分析
把源码转化为抽象语法树之后,编译器就可以对这个树进行分析处理。静态分析会对代码进行错误检查,如出现方法被调用但是未定义、定义但是未使用的变量等,以此提高代码质量。当然,还可以通过使用 Xcode 自带的静态分析工具(Product -> Analyze)
- 类型检查
在此阶段clang会做检查,最常见的是检查程序是否发送正确的消息给正确的对象,是否在正确的值上调用了正常函数。如果你给一个单纯的NSObject*
对象发送了一个hello
消息,那么 clang 就会报错,同样,给属性设置一个与其自身类型不相符的对象,编译器会给出一个可能使用不正确的警告。一般会把类型分为两类:动态的和静态的。动态的在运行时做检查,静态的在编译时做检查。以往,编写代码时可以向任意对象发送任何消息,在运行时,才会检查对象是否能够响应这些消息。由于只是在运行时做此类检查,所以叫做动态类型。
至于静态类型,是在编译时做检查。当在代码中使用 ARC 时,编译器在编译期间,会做许多的类型检查:因为编译器需要知道哪个对象该如何使用。
- 其他分析
ObjCUnusedIVarsChecker.cpp
是用来检查是否有定义了,但是从未使用过的变量。(
This file defines a CheckObjCUnusedIvars, a checker that analyzes an Objective-C class's interface/implementation to determine if it has any ivars that are never accessed.
)
ObjCSelfInitChecker.cpp
是检查在 你的初始化方法中中调用self
之前,是否已经调用[self initWith...]
或[super init]
了(
This checks initialization methods to verify that they assign 'self' to the result of an initialization call (e.g. [super init], or [self initWith..]) before using 'self' or any instance variable.
)。
(五)中间代码生成和优化
LLVM IR有3种表示形式,但本质上是等价的。
- text:便于阅读的文本格式,类似于汇编语言,拓展名 .ll
- memory:内存格式
- bitcode:二进制格式,拓展名 .bc
我们对下面代码使用clang -O3 -S -emit-llvm main.m -o main.ll
,生成main.ll
:
#import <Foundation/Foundation.h>
#define a1 1
int sum(int a, int b)
int c = a + b;
return c;
int main(int argc, const char * argv[])
@autoreleasepool
// insert code here...
NSLog(@"Hello, World!");
int a = 5;
NSLog(@"%d", sum(a1, a));
return 0;
; ModuleID = 'main.m'
source_filename = "main.m"
target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128"
target triple = "arm64-apple-macosx12.0.0"
%struct.__NSConstantString_tag = type i32*, i32, i8*, i64
@__CFConstantStringClassReference = external global [0 x i32]
@.str = private unnamed_addr constant [14 x i8] c"Hello, World!\\00", section "__TEXT,__cstring,cstring_literals", align 1
@_unnamed_cfstring_ = private global %struct.__NSConstantString_tag i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str, i32 0, i32 0), i64 13 , section "__DATA,__cfstring", align 8 #0
@.str.1 = private unnamed_addr constant [3 x i8] c"%d\\00", section "__TEXT,__cstring,cstring_literals", align 1
@_unnamed_cfstring_.2 = private global %struct.__NSConstantString_tag i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str.1, i32 0, i32 0), i64 2 , section "__DATA,__cfstring", align 8 #0
; Function Attrs: norecurse nounwind readnone ssp uwtable willreturn
define i32 @sum(i32 %0, i32 %1) local_unnamed_addr #1
%3 = add nsw i32 %1, %0
ret i32 %3
; Function Attrs: ssp uwtable
define i32 @main(i32 %0, i8** nocapture readnone %1) local_unnamed_addr #2
%3 = tail call i8* @llvm.objc.autoreleasePoolPush() #3
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*))
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_.2 to i8*), i32 6)
tail call void @llvm.objc.autoreleasePoolPop(i8* %3)
ret i32 0
; Function Attrs: nounwind
declare i8* @llvm.objc.autoreleasePoolPush() #3
declare void以上是关于[iOS开发]编译过程的主要内容,如果未能解决你的问题,请参考以下文章
GCC编译器原理------编译原理三:编译过程(2-1)---编译之词法分析