主要内容:
C、C++以及OC的关系LLVM与CLang iOS编译流程C语言是一门面向过程的计算机编程语言,既可用于系统软件开发,也适用于应用软件开发;C语言编译器普遍存在于各种不同的操作系统中,例如Microsoft Windows,Mac OS X, Linux, Unix等;C语言的设计影响了众多后来的编程语言,例如C++、Objective-C、Java、C#等;C语言面向过程特点,但又进行了扩充和完善;C语言的能力,使其具备面向对象设计的能力,相当于C的超集;OC代码中也可以有C和C++语句,它可以调用C函数,也可以通过C++对象访问方法;OC与C++都是从C语言演变而来面向对象设计语言,也都兼容标准的C语言;但它们属于不同的面向对象学派;OC提供了运行时的动态绑定机制,而C++是编译时静态绑定,并通过嵌入类和虚函数来模拟实现;OC在编译阶段降低了编译要求提高了灵活性,而C++则是提高了编译要求,在编译过程中就发现更多的潜在错误,在运行前改正,降低了灵活性;以下面的代码为例,在编译期间,C++认为是错误的,而OC则认为没有问题:
NSString *test =(id) [[NSArray alloc] init];OC与C++在使用细节上的不同如下:
OC是动态定型,可以允许根据字符串名字来访问方法和类,还可以动态链接和添加类;OC不支持多继承,C++支持多继承;OC通过消息传递实现函数调用,而C++直接进行函数调用;OC采用Protocol形式来定义接口,而C++采用虚函数形式来定义接口;OC不允许同一个类中两个方法有相同的名字(即使只是参数类型不同),但C++可以;Objective-C属于编译型语言,这是为了保证iPhone的执行效率;
编译器生成机器码,机器码直接通过CPU执行,运行时不需要重新翻译;C、C++、OC等;Javascript、Python等;
编译原理-语言的分类
概念:把一种编程语言(原始语言)转换为另一种编程语言(目标语言)的程序;
大多数编译器都分前端和后端两部分:
词法分析、语法分析、生成中间代码;中间代码作为输入,进行与架构无关的代码优化,接着针对不同架构生成不同的机器码;补充:
中间代码作为媒介,使得前后端可以独立的变化,互不影响;CPU架构只需要修改后端即可;LLVM是苹果当前使用的编译器:
LLVM是一套编译器基础设施项目,为自由软件,以C++写成,包含一系列模块化的编译器组件和工具链,用来开发编译器前端和后端;LLVM 衍生出了一些强大的子项目,比如:Clang 和 LLDB。CLang基于LLVM,是一个高度模块化开发的轻量级编译器;
CLang主要来自苹果电脑的支持,同时支持C、Objective-C以及C++;CLang用于替代Xcode5版本前使用的GCC,编译速度提高了3倍:iOS开发中,通常LLVM被认为是编译器的后端,而Clang是作为编译器的前端;IR(中间代码)作为媒介,这样前后端分离,使得前后端可以独立的变化,互不影响;C 语言家族的前端是 clang,swift 的前端是 swiftc,但二者的后端都是 LLVM;LLVM的编译过程相当复杂,iOS代码运行需要经过:预处理、编译、汇编、链接四个关键阶段,具体的流程如下图:

编译原理-编译流程
以OC语言为例,详细分析代码的编译流程,准备一个main.m文件的内容如下:
#import <Foundation/Foundation.h>
/// 增加注释:宏定义Name
#define Name "梧雨北辰"
int main(int argc, const char * argv[]) {
NSLog(@"Hello, %s", Name);
return 0;
}#include包含的文件插入到该指令位置等;// 、/* */等;#if、#ifdef,#endif等类似的条件编译;使用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;
}结果分析:
Foundation.h),而且这个过程是递归的;主要功能:通过扫描器,分割识别源代码符号(如大小括号、=、字符串);
使用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>结果分析:
Loc=<main.m:4:1> 就表示:'int'这个符号是从源文件main.m的第4行的第1个字符开始的;主要功能:对源代码符号进行分析,验证语法是否正确,最后生成AST语法树;
使用xcrun命令,查看语法分析结果:
xcrun clang -fsyntax-only -Xclang -ast-dump main.c | open -fAST语法树:
IR(中间代码);主要功能:对AST树进行遍历分析,包括类型检查、方法实现检查,会及时提示错误;
主要功能:CodeGen负责将AST语法树自顶向下遍历,逐步翻译成IR中间代码;
IR中间代码:
iOS系统来说,IR中间代码生成的就是Mach-O可执行文件;IR是前端的输出,后端的输入;输出中间代码标志着前端工作的完成,接下来将进入后端的处理流程。
中间代码IR进入后端,LLVM会对其进行优化:
Optimization LevelbitcodeLLVM对IR进行优化后,会针对不同架构生成不同汇编代码;
汇编阶段的目的:
.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;
该阶段是汇编器将汇编代码转换为机器代码,并输出目标文件,即.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类型,即目标文件类型:
Mach-O文件是用于iOS和OS平台上的文件类型;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符号:
undefined:表示在当前文件暂时找不到符号_NSLog;external:表示这个符号是外部可以访问的,对应表示文件私有的符号是non-external;主要功能:符号解析、重定位、合并目标文件,最终生成可执行文件;
xcrun clang main.o -o main% 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类型了,即可执行文件。而且运行该文件后也打印出了预期的结果;
% 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,会在运行时动态绑定;
1.符号解析
将每个符号引用和对应的符号定义关联起来;
"ld:dumplicate symbols";"Undefined symbols";2.重定位
将变量名、函数名这些符号定义与一个内存位置关联起来;
3.合并目标文件
将多个.m文件编译产生的.o目标文件与其他Mach-O文件(如dylib、a、tbd),合成一个Mach-O格式的可执行文件;
变量和接口函数就会产生相互依赖关系;静态链接:作用于编译期,链接后的文件依然可能会存在一些"undefined"的符号。但是这些符号都会被记录下来,在运行时再通过dlopen和dlsym动态链接绑定;
动态链接:作用于运行时,这样的优势在于:诸多类似UIKit这样的共享库将不必包含在每一个App包里。比如:我们使用到的UIKit系统库,等到点击App真正开始运行之前,才会去链接依赖的UIKit,链接完成再运行App;