OC底層之編譯

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)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缰冤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子喳魏,更是在濱河造成了極大的恐慌棉浸,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刺彩,死亡現(xiàn)場(chǎng)離奇詭異迷郑,居然都是意外死亡枝恋,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門嗡害,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)焚碌,“玉大人,你說(shuō)我怎么就攤上這事霸妹∈纾” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵叹螟,是天一觀的道長(zhǎng)鹃骂。 經(jīng)常有香客問(wèn)我,道長(zhǎng)罢绽,這世上最難降的妖魔是什么畏线? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮良价,結(jié)果婚禮上寝殴,老公的妹妹穿的比我還像新娘。我一直安慰自己明垢,他們只是感情好杯矩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著袖外,像睡著了一般史隆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上曼验,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天泌射,我揣著相機(jī)與錄音,去河邊找鬼鬓照。 笑死熔酷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的豺裆。 我是一名探鬼主播拒秘,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼臭猜!你這毒婦竟也來(lái)了躺酒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蔑歌,失蹤者是張志新(化名)和其女友劉穎羹应,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體次屠,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡园匹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年雳刺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裸违。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掖桦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出供汛,到底是詐尸還是另有隱情枪汪,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布紊馏,位于F島的核電站料饥,受9級(jí)特大地震影響蒲犬,放射性物質(zhì)發(fā)生泄漏朱监。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一原叮、第九天 我趴在偏房一處隱蔽的房頂上張望赫编。 院中可真熱鬧,春花似錦奋隶、人聲如沸擂送。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嘹吨。三九已至,卻和暖如春境氢,著一層夾襖步出監(jiān)牢的瞬間蟀拷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工萍聊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留问芬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓寿桨,卻偏偏與公主長(zhǎng)得像此衅,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子亭螟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容