[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开发]编译过程的主要内容,如果未能解决你的问题,请参考以下文章

编译原理词法分析 java简单实现

GCC编译器原理------编译原理三:编译过程(2-1)---编译之词法分析

编译原理词法分析

前端开发 - JavaScript 词法分析

66.javac 编译与 JIT 编译编译过程javac 编译词法语法分析填充符号表语义分析字节码生成JIT 编译

通过实际的例子,介绍编译器的工作过程