1声功、編譯
OC屬于高級(jí)語(yǔ)言烦却,需要翻譯成計(jì)算機(jī)可以識(shí)別的機(jī)器碼,所以就需要用到了編譯
- 編譯過(guò)程:
源文件(.h .m .cpp)--> 預(yù)編譯期 --> 編譯期(詞法分析先巴、語(yǔ)法分析其爵、靜態(tài)分
析)--> 生成中間代碼和優(yōu)化 --> 匯編 --> 靜態(tài)鏈接(ldb)
- 編譯器
- LLVM項(xiàng)目是模塊化、可重用的編譯器以及工具鏈技術(shù)的集合
- LLVM開始成長(zhǎng)之后伸蚯,成為眾多編譯工具及低級(jí)工具技術(shù)的統(tǒng)稱摩渺,使得這個(gè)名字變得更不貼切,開發(fā)者因而決定放棄這個(gè)縮寫的意涵剂邮,現(xiàn)今LLVM已單純成為一個(gè)品牌摇幻,適用于LLVM下的所有項(xiàng)目,包含LLVM中介碼(LLVM IR)、LLVM除錯(cuò)工具绰姻、LLVM C++標(biāo)準(zhǔn)庫(kù)等
- Xcode就是采用LLVM作為默認(rèn)的編譯器
編譯的三段式設(shè)計(jì):前端 -- 優(yōu)化器 -- 后端- 前端枉侧,對(duì)源碼做詞法分析、語(yǔ)法分析狂芋、語(yǔ)義分析棵逊、生成中間代碼
- 優(yōu)化器,用于中間代碼優(yōu)化
- 后端银酗,用于生成機(jī)器碼
- LLVM 架構(gòu)
- 廣義的LLVM:整個(gè)LLVM架構(gòu)辆影;狹義的LLVM:LLVM后端(代碼優(yōu)化、目標(biāo)代碼生成等)
- 前端負(fù)責(zé)生成中間代碼也就是bitcode,LLVM下黍特,不同的語(yǔ)言有不同的編譯器前端蛙讥,OC的是clang
- 不同的前后端優(yōu)化使用統(tǒng)一的中間代碼LLVM IR(Intermediate Representation),對(duì)bitcode進(jìn)行各種類型的優(yōu)化灭衷,將bitcode代碼進(jìn)行一些邏輯的轉(zhuǎn)換次慢,使得代碼效率更高,體積更小
- 后端翔曲,也叫CodeGenerator迫像,負(fù)責(zé)把優(yōu)化后的bitcode編譯為指定目標(biāo)架構(gòu)的機(jī)器碼,比如 X86 Backend負(fù)責(zé)把bitcode編譯為x86指令集的機(jī)器碼
- 三段式的優(yōu)勢(shì)瞳遍,LLVM體系中闻妓,不同語(yǔ)言源代碼將會(huì)被轉(zhuǎn)化為統(tǒng)一的bitcode格式,三個(gè)模塊相互獨(dú)立掠械,可以充分復(fù)用由缆。比如,如果開發(fā)一門新的語(yǔ)言猾蒂,只要制造一個(gè)該語(yǔ)言的前端均唉,將源碼編譯為bitcode,優(yōu)化和后端不用管肚菠。同理舔箭,如果新的芯片架構(gòu)問(wèn)世,只需基于LLVM重新編寫一套目標(biāo)平臺(tái)的后端即可
Clang
- Clang 是一個(gè)由Apple主導(dǎo)編寫蚊逢,基于LLVM的C/C++/Objective-C輕量級(jí)編譯器层扶。源代碼發(fā)布于LLVM BSD協(xié)議下。 Clang將支持其普通lambda表達(dá)式时捌、返回類型的簡(jiǎn)化處理以及更好的處理constexpr關(guān)鍵字怒医。
- 它與GNU C語(yǔ)言規(guī)范幾乎完全兼容(當(dāng)然炉抒,也有部分不兼容的內(nèi)容奢讨, 包括編譯命令選項(xiàng)也會(huì)有點(diǎn)差異),并在此基礎(chǔ)上增加了額外的語(yǔ)法特性,比如C函數(shù)重載 (通過(guò)attribute((overloadable))來(lái)修飾函數(shù))拿诸,其目標(biāo)(之一)就是超越GCC扒袖。
- LLVM項(xiàng)目的一個(gè)子項(xiàng)目
- 基于LLVM的OC/C/C++/OC++的編譯器前端
- 編譯速度更快
- 內(nèi)存占用小,Clang生成的AST所占用的內(nèi)存是GCC的五分之一左右
- 模塊化設(shè)計(jì):Clang采用基于庫(kù)的模塊化設(shè)計(jì)亩码,易于IDE集成及其他用途的重用
- 診斷信息可讀性強(qiáng):在編譯過(guò)程中季率,Clang創(chuàng)建并保留了大量詳細(xì)的元數(shù)據(jù)(metadata),有利于調(diào)試和錯(cuò)誤報(bào)告
- 設(shè)計(jì)清晰簡(jiǎn)單描沟,容易理解飒泻,易于擴(kuò)展增強(qiáng)
客觀的說(shuō)GCC也有很多優(yōu)點(diǎn):例如支持多平臺(tái),基于C無(wú)需 C++編譯器即可編譯吏廉。這個(gè)優(yōu)點(diǎn)到蘋果那里反而成了缺點(diǎn)泞遗,蘋果需要的是快。
//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
//以下兩種方式是通過(guò)指定架構(gòu)模式的命令行,使用xcode工具 xcrun
//3佩伤、模擬器文件編譯
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
//4聊倔、真機(jī)文件編譯
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp
編譯過(guò)程
- 預(yù)處理會(huì)替進(jìn)行頭文件引入,宏替換生巡,注釋處理耙蔑,條件編譯(#ifdef)等操作。
- Clang前端負(fù)責(zé)分析源代碼語(yǔ)法分析孤荣,語(yǔ)義分析纵潦,并構(gòu)建針對(duì)該語(yǔ)言的抽象語(yǔ)法樹(AST)
- 詞法分析器讀入源文件的字符流,將他們組織稱有意義的詞素(lexeme)序列垃环,對(duì)于每個(gè)詞素邀层,此法分析器產(chǎn)生詞法單元(token)作為輸出
- 語(yǔ)法分析,詞法分析的Token流會(huì)被解析成一顆抽象語(yǔ)法樹(abstract syntax tree - AST)
- AST 是抽象語(yǔ)法樹遂庄,結(jié)構(gòu)上比代碼更精簡(jiǎn)寥院,遍歷起來(lái)更快,所以使用 AST 能夠更快速地進(jìn)行靜態(tài)檢查涛目,同時(shí)還能更快地生成 LLVM IR(中間代碼)
- 最后 AST 會(huì)生成 LLVM IR秸谢,LLVM IR 是一種更接近機(jī)器碼的語(yǔ)言,區(qū)別在于和平臺(tái)無(wú)關(guān)霹肝,通過(guò) IR 可以生成多份適合不同平臺(tái)的機(jī)器碼估蹄。對(duì)于 iOS 系統(tǒng),IR 生成的可執(zhí)行文件就是 Mach-O
- 優(yōu)化器統(tǒng)一對(duì)前端生成的中間代碼LLVM IR進(jìn)行代碼優(yōu)化
- LLVM 對(duì)中間代碼IR進(jìn)行優(yōu)化沫换,并針對(duì)不同架構(gòu)生成目標(biāo)文件臭蚁,以匯編代碼的形式進(jìn)行輸出
- 匯編階段,將上一步生成的匯編代碼轉(zhuǎn)化成機(jī)器代碼,最終以.o文件進(jìn)行輸出
- 鏈接器ldb(靜態(tài)鏈接),將匯編生成的.o文件和(dylib垮兑、.a冷尉、tbd)文件進(jìn)行鏈接生成可執(zhí)行文件(Mac-O)
預(yù)編譯
預(yù)編譯過(guò)程主要處理源代碼文件中以#
開始的預(yù)編譯指令
- 將所有的 “#define”刪除,并且展開所有的宏定義
- 處理所有條件預(yù)編譯指令系枪,比如“#if”雀哨、“#ifder”, “#elif”, “#else”,
"#endif" - 處理 “#include”預(yù)編譯指令私爷,將被包含的文件插入到該預(yù)編譯指令的位置雾棺。注意,這個(gè)過(guò)程是遞歸進(jìn)行的衬浑,也就是說(shuō)被包含的文件可能還包含其他文件垢村。
- 刪除所有的注釋
//
和/**/
, - 添加行號(hào)和文件名標(biāo)識(shí),比如#2“hello.c”2嚎卫,以便于編譯時(shí)編譯器產(chǎn)生調(diào)試用的行號(hào)
信息及用于編譯時(shí)產(chǎn)生編澤錯(cuò)誤或警告時(shí)能夠顯示行號(hào)嘉栓。 - 保留所有的
#pragma
編譯器指令,因?yàn)榫幾g器須要使用它們拓诸。
經(jīng)過(guò)預(yù)編譯后的.i
文件不包含任何宏定義侵佃,因?yàn)樗械暮昙航?jīng)被展開,并且包含的文件
也已經(jīng)被插入到.i
文件中奠支。所以當(dāng)我們無(wú)法判斷宏定義是否正確或頭文件包含是否正確時(shí)馋辈,可以查看預(yù)編譯后的文件來(lái)確定問(wèn)題。
- 預(yù)編譯指令
宏定義:
1.
不含參數(shù): #define MaxF 1
2.
含有參數(shù):#define SUMM(a,b) a+b
宏定義說(shuō)明:
1.
宏定義是用宏名代替一個(gè)字符串倍谜,只作簡(jiǎn)單置換迈螟,不作正確性檢查,同時(shí)也不會(huì)做運(yùn)算邏輯處理,同時(shí)在進(jìn)行宏定義時(shí)尔崔,可以引用已定義的宏名答毫,可以層層置換。(在這里需要特別注意的是:當(dāng)宏涉運(yùn)算時(shí)季春,要根據(jù)情況來(lái)添加括號(hào)洗搂,防止運(yùn)算邏輯出現(xiàn)錯(cuò)誤,#define SUMM(a,b) a+b
,在引用SUMM
進(jìn)行運(yùn)算時(shí)容易出錯(cuò)
2.
宏定義不是C語(yǔ)句载弄,不必在行末加分號(hào)耘拇。如果加了分號(hào)則會(huì)連分號(hào)一起進(jìn)行置換
3.
宏定義是專門用于預(yù)處理命令的一個(gè)專用名詞,它與定義變量的含義不同宇攻,只作字符替換惫叛,不分配內(nèi)存空間
4.
#define
命令出現(xiàn)在程序中函數(shù)的外面,宏名的有效范圍為定義命令之后到本文件結(jié)束逞刷。通常#define
命令寫在文件開頭嘉涌,函數(shù)之前妻熊,作為文件一部分,在此文件范圍內(nèi)有效
- 條件編譯
條件編譯就是在編譯之前預(yù)處理器根據(jù)預(yù)處理指令判斷對(duì)應(yīng)的條件洛心,如果條件滿足就將對(duì)應(yīng)的代碼編譯進(jìn)去,否則代碼就根本不進(jìn)入編譯環(huán)節(jié)(相當(dāng)于根本就沒(méi)有這段代碼)
1.
#if
編譯預(yù)處理中的條件命令, 相當(dāng)于C語(yǔ)法中的if語(yǔ)句
2.
#ifdef
判斷某個(gè)宏是否被定義, 若已定義, 執(zhí)行隨后的語(yǔ)句
3.
#ifndef
與#ifdef
相反, 判斷某個(gè)宏是否未被定義
4.
#elif
若#if
,#ifdef
,#ifndef
或前面的#elif
條件不滿足, 則執(zhí)行#elif
之后的語(yǔ)句, 相當(dāng)于C語(yǔ)法中的else-if
6.
#else
與#if
,#ifdef
,#ifndef
對(duì)應(yīng), 若這些條件不滿足, 則執(zhí)行#else
之后的語(yǔ)句, 相當(dāng)于C語(yǔ)法中的else
7.
#endif
#if
,#ifdef
,#ifndef
這些條件命令的結(jié)束標(biāo)志.
8.
#if
與#ifdef
的區(qū)別:#if是判斷后面的條件語(yǔ)句是否成立题篷,#ifdef
是判斷某個(gè)宏是否被定義過(guò)
#ifdef MAX_F
// 如果定義了宏MAX_F词身,則編譯此處的代碼
#else
// 如果沒(méi)有定義宏MAX_F,則編譯此處的代碼
#endif
// 同樣
#ifndef MAX_F
// 如果沒(méi)有定義宏MAX_F番枚,則編譯此處的代碼
#elif MAX_INT
// 如果定義了宏MAX_F,同時(shí)還定義了宏MAX_INT法严,則編譯此處的代碼
#else
// 定義了宏MAX_F,但是沒(méi)有定義宏MAX_INT,則編譯此處的代碼
#endif
為了防止該頭文件被引用時(shí)發(fā)生重復(fù)引用
#ifndef Header_h
#define Header_h
#endif
- 文件包含
C語(yǔ)言下一般使用
#include
, OC中一般使用#import
,它們的區(qū)別是:在使用#include的時(shí)候要注意處理重復(fù)引用葫笼,#import大部分功能和#include是一樣的,但是他處理了重復(fù)引用的問(wèn)題,我們?cè)谝梦募臅r(shí)候不用再去自己進(jìn)行重復(fù)引用處理深啤。OC中還有一個(gè)引用聲明 @class主要是用于聲明一個(gè)類,告訴編譯器它后面的名字是一個(gè)類的名字,而這個(gè)類的定義實(shí)現(xiàn)是暫時(shí)不用知道的。一般來(lái)說(shuō),在interface中(.h文件)引用一個(gè)類,就用@class,它會(huì)把這個(gè)類作為一個(gè)類型來(lái)使用,而在實(shí)現(xiàn)這個(gè)interface的文件中,如果需要引用這個(gè)類的實(shí)體變量或者方法之類的,還是需要import這個(gè)在@class中聲明的類
@class僅僅是告訴編譯器有這么一個(gè)類, 具體這個(gè)類里有什么信息, 完全不知
使用include不會(huì)檢測(cè)之前有沒(méi)有對(duì)這個(gè)頭文件進(jìn)行包含路星,所以一般都有一個(gè)宏控制來(lái)防止頭文件被多次包含溯街,不過(guò)現(xiàn)在新建頭文件時(shí)編譯器都會(huì)自動(dòng)生成一段
而使用import則不必考慮,它會(huì)自動(dòng)檢測(cè)所包含的頭文件在之前有沒(méi)有被包含洋丐,如果已被包含則不再包含呈昔。在object-c中一般都是用import。
編譯
編譯器其實(shí)就是將高級(jí)語(yǔ)言翻譯成機(jī)器語(yǔ)言的過(guò)程
匯編
匯編器是將匯編代碼轉(zhuǎn)變成機(jī)器可以執(zhí)行的指令友绝,每—個(gè)匯編語(yǔ)句幾乎都對(duì)應(yīng)一條機(jī)器
指令堤尾。所以匯編器的匯編過(guò)程相對(duì)于編譯器來(lái)講比較簡(jiǎn)單,它沒(méi)有復(fù)雜的語(yǔ)法迁客,也沒(méi)有語(yǔ)義郭宝,也不需要做指令優(yōu)化,只是根據(jù)匯編指令和機(jī)器指令的對(duì)照表一一翻譯即可掷漱,最終生成.o
目標(biāo)文件(object file)
知識(shí)點(diǎn)擴(kuò)展
'iOS 芯片架構(gòu)指令集'
1粘室、armv7|armv7s|arm64都是ARM處理器的指令集
2、i386|x86_64 是Mac處理器的指令集
3卜范、對(duì)應(yīng)設(shè)備:
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是針對(duì)intel通用微處理器32位處理器
x86_64是針對(duì)x86架構(gòu)的64位處理器
模擬器32位處理器測(cè)試需要i386架構(gòu)育特,
模擬器64位處理器測(cè)試需要x86_64架構(gòu),
真機(jī)32位處理器需要armv7,或者armv7s架構(gòu)先朦,
真機(jī)64位處理器需要arm64架構(gòu)