编译可以分为四步:
过程是源文件main.c和相关头文件被(stdio.h)被预编译器cpp预编译成一个.i文件
使用命令:clang -E main.m
或在Xcode的Product->Perform Action->Preprocess得到预编译结果。
#import #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); Person* person = [[Person alloc] init]; } return 0; }
预编译后
# 187 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3 # 9 "main.m" 2 # 1 "./Person.h" 1 # 10 "./Person.h" #pragma clang assume_nonnull begin @interface Person : NSObject @property NSString* name; @end # 14 "./Person.h" #pragma clang assume_nonnull end # 10 "main.m" 2 int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"Hello, World!"); Person* person = [[Person alloc] init]; person.name = @"111"; } return 0; }
预编译主要是处理源代码中以“#”开头的预编译指令:
"define"
删除并展开对应宏定义#if/#ifdef/#else/#endif
"#include/#import"
包含的文件递归插入此处"//或/**/"
“#1"main.m"”
,编译调试会用到编译过程就是把预处理完的文件进行一系列的:词法分析
、语法分析
、语义分析
及优化后生产相应的汇编代码文件,此过程是整个程序构建的核心部分,也是最复杂的部分之一。
其编译过程相当于如下命令:
clang -S main.i -o main.s
使用命令clang -Xclang -dump-tokens main.m
star '*' Loc= identifier 'person' [LeadingSpace] Loc= equal '=' [LeadingSpace] Loc= l_square '[' [LeadingSpace] Loc= l_square '[' Loc= identifier 'Person' Loc= identifier 'alloc' [LeadingSpace] Loc= r_square ']' Loc= identifier 'init' [LeadingSpace] Loc= r_square ']' Loc= semi ';' Loc=
执行 clang 命令 clang -Xclang -ast-dump -fsyntax-only main.m
抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并不会表示出真实语法出现的每一个细节,比如说,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现。
| | `-ObjCMessageExpr 0x130078978 'Person *' selector=alloc class='Person' | `-PseudoObjectExpr 0x130078b50 'NSString *' | |-BinaryOperator 0x130078af8 'NSString *' '=' | | |-ObjCPropertyRefExpr 0x130078ab0 '' lvalue objcproperty Kind=PropertyRef Property="name" Messaging=Setter | | | `-OpaqueValueExpr 0x130078a98 'Person *' | | | `-ImplicitCastExpr 0x130078a10 'Person *' | | | `-DeclRefExpr 0x1300789f0 'Person *' lvalue Var 0x130078900 'person' 'Person *' | | `-OpaqueValueExpr 0x130078ae0 'NSString *' | | `-ObjCStringLiteral 0x130078a78 'NSString *' | | `-StringLiteral 0x130078a58 'char[4]' lvalue "111"
使用命令clang -O3 -S -emit-llvm main.m -o main.ll
,
@__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 @"OBJC_CLASS_$_Person" = external global %struct._class_t @"OBJC_CLASSLIST_REFERENCES_$_" = internal global %struct._class_t* @"OBJC_CLASS_$_Person", section "__DATA,__objc_classrefs,regular,no_dead_strip", align 8 @.str.1 = private unnamed_addr constant [4 x i8] c"111\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 ([4 x i8], [4 x i8]* @.str.1, i32 0, i32 0), i64 3 }, section "__DATA,__cfstring", align 8 #0 @llvm.compiler.used = appending global [1 x i8*] [i8* bitcast (%struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_" to i8*)], section "llvm.metadata"
使用命令xcrun clang -S -o - main.m | open -f
.section __TEXT,__text,regular,pure_instructions .build_version macos, 13, 0 sdk_version 13, 1 .globl _main ; -- Begin function main .p2align 2 _main: ; @main .cfi_startproc ; %bb.0: sub sp, sp, #64 stp x29, x30, [sp, #48] ; 16-byte Folded Spill add x29, sp, #48 .cfi_def_cfa w29, 16 .cfi_offset w30, -8 .cfi_offset w29, -16 ; implicit-def: $x8 mov w8, #0 str w8, [sp, #20] ; 4-byte Folded Spill stur wzr, [x29, #-4] stur w0, [x29, #-8] stur x1, [x29, #-16]
汇编就是把上面得到的.s文件里的汇编指令一一翻译为机器语言。汇编器的汇编过程相较于编译器来讲比较简单,只是根据汇编指令和机器指令的对照表一一翻译就可以了。
汇编指令:
clang -c main.s -o main.o
链接指令:
clang main.o -o main
现在程序为了便于维护都是分模块组成,比如一个App,对应有多个源代码文件。每个源代码文件汇编成目标文件,根据上面流程A目标文件访问B目标文件的函数或者变量,是不知道地址的,链接就是要解决这个问题。链接过程主要包括地址和空间分配、符号决议和重定位。
链接就是把目标文件(一个或多个)和需要的库(静态库/动态库)链接成可执行文件。后面会分别讲静态链接和动态链接。
动态库在程序编译链接的时候,如下图所示:
静态库和动态库都是程序编译好的二进制文件,苹果官方的解释:
存在形式:
“.a”
或者 “.framework”
为文件后缀名;.a 是一个纯二进制文件,.framework 中除了有二进制文件之外还有资源文件。.a 要有 .h 文件以及资源文件配合,.framework 文件可以直接使用。总的来说,.a + .h + sourceFile = .framework,因此创建静态库最好还是用 .framework 的形式。“.dylib”
或者 “.framework”
为文件后缀名(Xcode7 之后 .tbd 替代 .dylib)。使用区别
因为整个数据库的代码都被整合到目标代码中,则编译成的文件比较大。编译后的执行程序不再需要外部的数据库支持。如果静态数据库改变了,那么程序必须重新编译。
动态库链接时不复制,程序运行时有系统动态加载到内存中,供程序调用,而且系统只加载一次,多个程序调用节省内存
动态库在编译时,并没有编译进目标代码,所以产生的可执行文件比较小。当程序运行时执行到相关函数才才动态申请并调用函数库的相应函数,所以程序的运行环境必须提供相应的库。且动态函数库的升级并不影响程序。
各种优点:
静态库:
动态库在程序中是怎么加载到内存呢?系统是怎么链接的?这就要用到dyld(the dymanic link editor)动态链接器
dyld(the dymanic link editor)动态链接器是苹果操作系统的一个重要组成部分,在系统内核XNU完成Mach- O文件的加载,做好程序准备工作后,加载或链接动态共享库或可执行文件确保程序能正确执行。
加载流程:
1,环境变量控制:根据环境的状态配置和环境变量下的相应的条件判断状态值及获取当前架构
2,共享缓存解析处理:检查是否开启了共享缓存及共享缓存是否映射到共享区域,例如UIKi t,CoreFoundation等
3,主程序初始化:调用instantiateFromLoadedImage函数实例化出一个ImageLoader对象
4,插入动态库:遍历环境变量DYLD_INSERT_LIBRARIES待插入动态库表容器组,调用loadInsertedDylib加载引入
5,链接主程序和动态库:进行符号和地址的绑定,加载所有类,最后执行load方法和clang attribute的constructor修饰函数
dyld2是如何加载程序的?
经过对dyld 2的优化,dyld 3的加载过程的不同之处