OC底层之编译

1、编译

OC属于高级语言,需要翻译成计算机可以识别的机器码,所以就需要用到了编译

  • 编译过程:
源文件(.h .m .cpp)--> 预编译期 --> 编译期(词法分析、语法分析、静态分
析)--> 生成中间代码和优化 --> 汇编 --> 静态链接(ldb)
  • 编译器
  • LLVM项目是模块化、可重用的编译器以及工具链技术的集合
  • LLVM开始成长之后,成为众多编译工具及低级工具技术的统称,使得这个名字变得更不贴切,开发者因而决定放弃这个缩写的意涵,现今LLVM已单纯成为一个品牌,适用于LLVM下的所有项目,包含LLVM中介码(LLVM IR)、LLVM除错工具、LLVM C++标准库等
  • Xcode就是采用LLVM作为默认的编译器
    编译的三段式设计:前端 -- 优化器 -- 后端
  • 前端,对源码做词法分析、语法分析、语义分析、生成中间代码
  • 优化器,用于中间代码优化
  • 后端,用于生成机器码
  • LLVM 架构
  • 广义的LLVM:整个LLVM架构;狭义的LLVM:LLVM后端(代码优化、目标代码生成等)
  • 前端负责生成中间代码也就是bitcode,LLVM下,不同的语言有不同的编译器前端,OC的是clang
  • 不同的前后端优化使用统一的中间代码LLVM IR(Intermediate Representation),对bitcode进行各种类型的优化,将bitcode代码进行一些逻辑的转换,使得代码效率更高,体积更小
  • 后端,也叫CodeGenerator,负责把优化后的bitcode编译为指定目标架构的机器码,比如 X86 Backend负责把bitcode编译为x86指令集的机器码
  • 三段式的优势,LLVM体系中,不同语言源代码将会被转化为统一的bitcode格式,三个模块相互独立,可以充分复用。比如,如果开发一门新的语言,只要制造一个该语言的前端,将源码编译为bitcode,优化和后端不用管。同理,如果新的芯片架构问世,只需基于LLVM重新编写一套目标平台的后端即可
  • Clang
  • Clang 是一个由Apple主导编写,基于LLVM的C/C++/Objective-C轻量级编译器。源代码发布于LLVM BSD协议下。 Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。
  • 它与GNU C语言规范几乎完全兼容(当然,也有部分不兼容的内容, 包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,比如C函数重载 (通过attribute((overloadable))来修饰函数),其目标(之一)就是超越GCC。
  • LLVM项目的一个子项目
  • 基于LLVM的OC/C/C++/OC++的编译器前端
  • 编译速度更快
  • 内存占用小,Clang生成的AST所占用的内存是GCC的五分之一左右
  • 模块化设计:Clang采用基于库的模块化设计,易于IDE集成及其他用途的重用
  • 诊断信息可读性强:在编译过程中,Clang创建并保留了大量详细的元数据(metadata),有利于调试和错误报告
  • 设计清晰简单,容易理解,易于扩展增强

客观的说GCC也有很多优点:例如支持多平台,基于C无需 C++编译器即可编译。这个优点到苹果那里反而成了缺点,苹果需要的是快。

//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp

//2、将 ViewController.m 编译成  ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m

//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//3、模拟器文件编译
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

//4、真机文件编译
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp 

编译过程

  • 预处理会替进行头文件引入,宏替换,注释处理,条件编译(#ifdef)等操作。
  • Clang前端负责分析源代码语法分析,语义分析,并构建针对该语言的抽象语法树(AST)
  • 词法分析器读入源文件的字符流,将他们组织称有意义的词素(lexeme)序列,对于每个词素,此法分析器产生词法单元(token)作为输出
  • 语法分析,词法分析的Token流会被解析成一颗抽象语法树(abstract syntax tree - AST)
  • AST 是抽象语法树,结构上比代码更精简,遍历起来更快,所以使用 AST 能够更快速地进行静态检查,同时还能更快地生成 LLVM IR(中间代码)
  • 最后 AST 会生成 LLVM IR,LLVM IR 是一种更接近机器码的语言,区别在于和平台无关,通过 IR 可以生成多份适合不同平台的机器码。对于 iOS 系统,IR 生成的可执行文件就是 Mach-O
  • 优化器统一对前端生成的中间代码LLVM IR进行代码优化
  • LLVM 对中间代码IR进行优化,并针对不同架构生成目标文件,以汇编代码的形式进行输出
  • 汇编阶段,将上一步生成的汇编代码转化成机器代码,最终以.o文件进行输出
  • 链接器ldb(静态链接),将汇编生成的.o文件和(dylib、.a、tbd)文件进行链接生成可执行文件(Mac-O)

预编译

预编译过程主要处理源代码文件中以#开始的预编译指令

  • 将所有的 “#define”删除,并且展开所有的宏定义
  • 处理所有条件预编译指令,比如“#if”、“#ifder”, “#elif”, “#else”,
    "#endif"
  • 处理 “#include”预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。
  • 删除所有的注释 ///**/,
  • 添加行号和文件名标识,比如#2“hello.c”2,以便于编译时编译器产生调试用的行号
    信息及用于编译时产生编泽错误或警告时能够显示行号。
  • 保留所有的#pragma编译器指令,因为编译器须要使用它们。

经过预编译后的.i文件不包含任何宏定义,因为所有的宏己经被展开,并且包含的文件
也已经被插入到.i文件中。所以当我们无法判断宏定义是否正确或头文件包含是否正确时,可以查看预编译后的文件来确定问题。

  • 预编译指令

宏定义:
1.不含参数: #define MaxF 1
2.含有参数:#define SUMM(a,b) a+b

宏定义说明:
1.宏定义是用宏名代替一个字符串,只作简单置换,不作正确性检查,同时也不会做运算逻辑处理,同时在进行宏定义时,可以引用已定义的宏名,可以层层置换。(在这里需要特别注意的是:当宏涉运算时,要根据情况来添加括号,防止运算逻辑出现错误,#define SUMM(a,b) a+b,在引用SUMM进行运算时容易出错
2.宏定义不是C语句,不必在行末加分号。如果加了分号则会连分号一起进行置换
3.宏定义是专门用于预处理命令的一个专用名词,它与定义变量的含义不同,只作字符替换,不分配内存空间
4. #define命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本文件结束。通常#define命令写在文件开头,函数之前,作为文件一部分,在此文件范围内有效

  • 条件编译

条件编译就是在编译之前预处理器根据预处理指令判断对应的条件,如果条件满足就将对应的代码编译进去,否则代码就根本不进入编译环节(相当于根本就没有这段代码)

1. #if 编译预处理中的条件命令, 相当于C语法中的if语句
2. #ifdef判断某个宏是否被定义, 若已定义, 执行随后的语句
3. #ifndef#ifdef相反, 判断某个宏是否未被定义
4. #elif#if, #ifdef, #ifndef或前面的#elif条件不满足, 则执行#elif之后的语句, 相当于C语法中的else-if
6. #else#if, #ifdef, #ifndef对应, 若这些条件不满足, 则执行#else之后的语句, 相当于C语法中的else
7. #endif #if, #ifdef, #ifndef这些条件命令的结束标志.
8. #if#ifdef 的区别:#if是判断后面的条件语句是否成立,#ifdef是判断某个宏是否被定义过

#ifdef  MAX_F
// 如果定义了宏MAX_F,则编译此处的代码
#else
// 如果没有定义宏MAX_F,则编译此处的代码
#endif

// 同样
#ifndef  MAX_F
// 如果没有定义宏MAX_F,则编译此处的代码
#elif MAX_INT
// 如果定义了宏MAX_F,同时还定义了宏MAX_INT,则编译此处的代码
#else
// 定义了宏MAX_F,但是没有定义宏MAX_INT,则编译此处的代码
#endif
为了防止该头文件被引用时发生重复引用

#ifndef Header_h 
#define Header_h 

#endif
  • 文件包含

C语言下一般使用 #include, OC中一般使用#import ,它们的区别是:在使用#include的时候要注意处理重复引用,#import大部分功能和#include是一样的,但是他处理了重复引用的问题,我们在引用文件的时候不用再去自己进行重复引用处理。OC中还有一个引用声明 @class主要是用于声明一个类,告诉编译器它后面的名字是一个类的名字,而这个类的定义实现是暂时不用知道的。一般来说,在interface中(.h文件)引用一个类,就用@class,它会把这个类作为一个类型来使用,而在实现这个interface的文件中,如果需要引用这个类的实体变量或者方法之类的,还是需要import这个在@class中声明的类
@class仅仅是告诉编译器有这么一个类, 具体这个类里有什么信息, 完全不知
使用include不会检测之前有没有对这个头文件进行包含,所以一般都有一个宏控制来防止头文件被多次包含,不过现在新建头文件时编译器都会自动生成一段
而使用import则不必考虑,它会自动检测所包含的头文件在之前有没有被包含,如果已被包含则不再包含。在object-c中一般都是用import。

编译

编译器其实就是将高级语言翻译成机器语言的过程

汇编

汇编器是将汇编代码转变成机器可以执行的指令,每—个汇编语句几乎都对应一条机器
指令。所以汇编器的汇编过程相对于编译器来讲比较简单,它没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译即可,最终生成.o目标文件(object file)

知识点扩展

'iOS 芯片架构指令集'

1、armv7|armv7s|arm64都是ARM处理器的指令集
2、i386|x86_64 是Mac处理器的指令集
3、对应设备:
arm64:iPhone6s | iphone6s plus|iPhone6| iPhone6 plus|iPhone5S | iPad Air| iPad mini2(iPad mini with Retina Display)
armv7s:iPhone5|iPhone5C|iPad4(iPad with Retina Display)
armv7:iPhone4|iPhone4S|iPad|iPad2|iPad3(The New iPad)|iPad mini|iPod Touch 3G|iPod Touch4

i386是针对intel通用微处理器32位处理器
x86_64是针对x86架构的64位处理器

模拟器32位处理器测试需要i386架构,
模拟器64位处理器测试需要x86_64架构,

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

推荐阅读更多精彩内容