iOS编译原理

主要内容:

  1. 理解CC++以及OC的关系
  2. 编译型语言与解释型语言
  3. 编译器LLVMCLang
  4. 理解iOS编译流程
  5. 预处理
  6. 编译
  7. 汇编
  8. 链接

一、理解C、C++以及OC的关系

1.C语言
  1. C语言是一门面向过程的计算机编程语言,既可用于系统软件开发,也适用于应用软件开发;
  2. C语言编译器普遍存在于各种不同的操作系统中,例如Microsoft Windows,Mac OS X, Linux, Unix等;
  3. C语言的设计影响了众多后来的编程语言,例如C++Objective-CJavaC#等;
2.C++语言
  1. 兼容了C语言面向过程特点,但又进行了扩充和完善;
  2. 作为一种面向对象的语言,具有封装、多继承、多态等特性;
3.Objective-C语言
  1. 扩展了C语言的能力,使其具备面向对象设计的能力,相当于C的超集;
  2. OC代码中也可以有CC++语句,它可以调用C函数,也可以通过C++对象访问方法;
4.OC与C++的比较
  1. OCC++都是从C语言演变而来面向对象设计语言,也都兼容标准的C语言;但它们属于不同的面向对象学派;
  2. 两者最大的不同在于:OC提供了运行时的动态绑定机制,而C++是编译时静态绑定,并通过嵌入类和虚函数来模拟实现;
  3. OC在编译阶段降低了编译要求提高了灵活性,而C++则是提高了编译要求,在编译过程中就发现更多的潜在错误,在运行前改正,降低了灵活性;

以下面的代码为例,在编译期间,C++认为是错误的,而OC则认为没有问题:

NSString *test =(id) [[NSArray alloc] init];

OCC++在使用细节上的不同如下:

  1. 定型:OC是动态定型,可以允许根据字符串名字来访问方法和类,还可以动态链接和添加类;
  2. 继承:OC不支持多继承,C++支持多继承;
  3. 函数调用:OC通过消息传递实现函数调用,而C++直接进行函数调用;
  4. 接口:OC采用Protocol形式来定义接口,而C++采用虚函数形式来定义接口;
  5. 重载:OC不允许同一个类中两个方法有相同的名字(即使只是参数类型不同),但C++可以;

二、编译型语言与解释型语言

Objective-C属于编译型语言,这是为了保证iPhone的执行效率;

1.编译型语言
  1. 程序运行前,必须先通过编译器生成机器码,机器码直接通过CPU执行,运行时不需要重新翻译;
  2. 程序执行效率高,但依赖编译器,调试周期长、跨平台性差些;
  3. 代表语言:CC++OC等;
2.解释型语言
  1. 程序运行前,不需要进行编译,而是以文本方式存储程序代码,运行时需要解释器解释后再运行;
  2. 程序执行效率低下,但是程序具有动态性,运行后也可以随时增加和更新代码来改变程序逻辑;
  3. 代表语言:JavascriptPython等;
编译原理-语言的分类

三、编译器LLVM与CLang

1.编译器

概念:把一种编程语言(原始语言)转换为另一种编程语言(目标语言)的程序;

大多数编译器都分前端后端两部分:

  • 前端:负责词法分析语法分析生成中间代码
  • 后端:以中间代码作为输入,进行与架构无关的代码优化,接着针对不同架构生成不同的机器码;

补充:

  1. 前后端以中间代码作为媒介,使得前后端可以独立的变化,互不影响;
  2. 这样的好处在于:新增一门语言只需要修改前端,而新增一种CPU架构只需要修改后端即可;
2.LLVM与Clang

LLVM是苹果当前使用的编译器:

  1. LLVM是一套编译器基础设施项目,为自由软件,以C++写成,包含一系列模块化的编译器组件和工具链,用来开发编译器前端后端
  2. 基于 LLVM 衍生出了一些强大的子项目,比如:ClangLLDB

CLang基于LLVM,是一个高度模块化开发的轻量级编译器;

  1. CLang主要来自苹果电脑的支持,同时支持CObjective-C以及C++
  2. CLang用于替代Xcode5版本前使用的GCC,编译速度提高了3倍:
3.理解iOS中的编译器
  1. iOS开发中,通常LLVM被认为是编译器的后端,而Clang是作为编译器的前端;
  2. 二者以 IR(中间代码)作为媒介,这样前后端分离,使得前后端可以独立的变化,互不影响;
  3. C 语言家族的前端是 clangswift 的前端是 swiftc,但二者的后端都是 LLVM

四、理解iOS编译流程

1.编译流程图

LLVM的编译过程相当复杂,iOS代码运行需要经过:预处理编译汇编链接四个关键阶段,具体的流程如下图:

编译原理-编译流程
2.准备测试文件

OC语言为例,详细分析代码的编译流程,准备一个main.m文件的内容如下:

#import <Foundation/Foundation.h>
/// 增加注释:宏定义Name
#define Name "梧雨北辰"
int main(int argc, const char * argv[]) {
    NSLog(@"Hello, %s", Name);
    return 0;
}

五、预处理(Prepressing)

1.主要功能
  1. 替换宏:替换代码中各种宏定义,如定义的常量、函数等;
  2. 导入头文件:将#include包含的文件插入到该指令位置等;
  3. 清理注释:删除所有注释:///* */等;
  4. 条件编译:处理#if#ifdef#endif等类似的条件编译;
  5. 添加行号和文件名标识:以便于编译时编译器能够显示警告和错误的所在行号;
2.查看预处理结果

使用xcrun命令,在终端执行预处理操作:

xcrun clang -E main.m

终端显示效果如下:

# 1 "main.m"
# 1 "<built-in>" 1

...

# 1 "/Applications/Xcode13.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/FoundationLegacySwiftCompatibility.h" 1 3
# 193 "/Applications/Xcode13.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3
# 2 "main.m" 2


int main(int argc, const char * argv[]) {
 NSLog(@"Hello, %s", "梧雨北辰");
 return 0;
}

结果分析:

  1. 预处理后的文件中,注释已经被清理,宏定义也已经被替换;
  2. 预处理后的文件有很多行,因为该过程中导入了头文件(Foundation.h),而且这个过程是递归的;

六、编译(Compilation)

1. 词法分析(Lexical Analysis)

主要功能:通过扫描器,分割识别源代码符号(如大小括号、=、字符串);

使用xcrun命令,在终端执行词法分析操作:

xcrun clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

终端显示效果如下:

annot_module_include '#import <Foundation/Foundation.h>
/'      Loc=<main.m:1:1>
int 'int'    [StartOfLine]  Loc=<main.m:4:1>
identifier 'main'    [LeadingSpace]

......

r_brace '}'  [StartOfLine]  Loc=<main.m:7:1>
eof ''      Loc=<main.m:10:1>

结果分析:

  1. 每个被分割的源代码符号都被记录了位置,方便后续定位错误;
  2. 比如Loc=<main.m:4:1> 就表示:'int'这个符号是从源文件main.m的第4行的第1个字符开始的;
2.语法分析(Semantic Analysis)

主要功能:对源代码符号进行分析,验证语法是否正确,最后生成AST语法树;

使用xcrun命令,查看语法分析结果:

xcrun clang -fsyntax-only -Xclang -ast-dump main.c | open -f

AST语法树:

  1. 是抽象语法树,结构上比代码更精简,遍历速度更快;
  2. 能够更快的进行静态检查,同时生成IR(中间代码);
3.静态分析(Static Analysis)

主要功能:对AST树进行遍历分析,包括类型检查方法实现检查,会及时提示错误;

4.生成中间代码(Code Generation)

主要功能:CodeGen负责将AST语法树自顶向下遍历,逐步翻译成IR中间代码;

IR中间代码:

  1. 这是一种更接近于机器码的语言,使得编译器被分为前端和后端,不同的平台可以利用各自的编译器将中间代码,转化为适合不同平台的机器码;
  2. 对于iOS系统来说,IR中间代码生成的就是Mach-O可执行文件;
  3. IR是前端的输出,后端的输入;

七、汇编(Assembly)

输出中间代码标志着前端工作的完成,接下来将进入后端的处理流程。

1.LLVM优化中间代码

中间代码IR进入后端,LLVM会对其进行优化:

  1. Optimization Level
  2. bitcode
2.生成汇编代码

LLVMIR进行优化后,会针对不同架构生成不同汇编代码;

汇编阶段的目的:

  1. 将代码汇编化,并将符号进行归类;
  2. 将外部导入符号,放到重定位符号表;
  3. 最后生成一个或多个.o目标文件;

使用xcrun命令,生成汇编文件:

xcrun clang -S main.m -o main.s

打开.s文件,摘取内容如下:

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 11, 0 sdk_version 11, 3
    .globl  _main                           ## -- Begin function main
    
    // ......

    callq   _NSLog

    // ......
.subsections_via_symbols

可以看到,汇编文件中的NSLog操作已经被转化为汇编命令形式的调用,即callq _NSLog

3.生成目标文件

该阶段是汇编器汇编代码转换为机器代码,并输出目标文件,即.o文件;

使用xcrun命令,生成目标文件:

xcrun clang -fmodules -c main.m -o main.o

使用file命令,查看目标文件类型:

% file main.o
main.o: Mach-O 64-bit object x86_64

可以看到,汇编器生成Mach-O格式的文件,而且是object类型,即目标文件类型:

  1. Mach-O文件是用于iOSOS平台上的文件类型;
  2. Mach-O作为a.out格式的替代,提供了更强的扩展性,也提升了符号表中信息的访问速度;

使用xcrun命令,查看下main.o中的符号:

xcrun nm -nm main.o

终端显示效果如下:

                 (undefined) external _NSLog
                 (undefined) external ___CFConstantStringClassReference
0000000000000000 (__TEXT,__text) external _main

可以看到,此时我们使用的NSLog函数,对应着_NSLog符号:

  1. undefined:表示在当前文件暂时找不到符号_NSLog
  2. external:表示这个符号是外部可以访问的,对应表示文件私有的符号是non-external

八、链接(Linking)

主要功能:符号解析、重定位、合并目标文件,最终生成可执行文件;

1.使用xcrun命令执行链接,得到可执行文件
xcrun clang main.o -o main
2.使用file命令,查看文件类型
% file main
main: Mach-O 64-bit executable x86_64
% ./main
2021-10-01 19:06:41.846 main[5663:660299] Hello, 梧雨北辰

结果分析:虽然还是Mach-O格式,但此时已经是executable类型了,即可执行文件。而且运行该文件后也打印出了预期的结果;

3.再次使用xcrun命令,查看可执行文件的符号表
% xcrun nm -nm main
                 (undefined) external _NSLog (from Foundation)
                 (undefined) external ___CFConstantStringClassReference (from CoreFoundation)
                 (undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100003f40 (__TEXT,__text) external _main
0000000100008008 (__DATA,__data) non-external __dyld_private

结果分析:_NSLog符号依然是undefined,不过此时多了一些信息,即from Foundation,表示这个符号来自于Foundation,会在运行时动态绑定;

4.链接阶段的主要任务

1.符号解析

将每个符号引用和对应的符号定义关联起来;

  • 链接器链接多文件时会创建符号表,用于记录所有已经定义和未定义的符号;
    1. 出现相同符号,会报错:"ld:dumplicate symbols"
    2. 在其他目标文件里没有找到到符号,会报错:"Undefined symbols"
  • 另外,链接器在整理函数的符号调用关系时,可以帮助我们理清那些函数没有被调用,并自动去除掉;

2.重定位

将变量名、函数名这些符号定义与一个内存位置关联起来;

  • 因为只有通过了绑定,机器才知道需要操作什么内存地址;
  • 否则,我们就需要在写代码时给每个指令设置好内存地址,不仅操作繁琐,而且容易引起出错;

3.合并目标文件

将多个.m文件编译产生的.o目标文件与其他Mach-O文件(如dylibatbd),合成一个Mach-O格式的可执行文件;

  • 通常项目都会包含多个文件,不同文件之间的变量接口函数就会产生相互依赖关系;
  • 程序运行前,需要使用链接器将多个文件里的符号和地址绑定起来,才能保证整个程序里的变量、接口的正常调用;
5.理解静态链接与动态链接

静态链接:作用于编译期,链接后的文件依然可能会存在一些"undefined"的符号。但是这些符号都会被记录下来,在运行时再通过dlopendlsym动态链接绑定;

动态链接:作用于运行时,这样的优势在于:诸多类似UIKit这样的共享库将不必包含在每一个App包里。比如:我们使用到的UIKit系统库,等到点击App真正开始运行之前,才会去链接依赖的UIKit,链接完成再运行App

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容