devc++源文件未编译_iOS 编译知识小结
- 簡介
- 源碼到可執行文件流程
- 編譯器
- 編譯流程
- 預處理(preprocessor)
- 詞法分析(lexical anaysis)
- 語法分析(semantic analysis)
- CodeGen
- 生成匯編代碼
- 生成目標文件
- 生成可執行文件
- Xcode中查看Clang編譯.m文件信息
- Xcode常見編譯報錯分析
- 1. duplicate symbols報錯
- 2. symbol(s) not found for architecture x86_64/arm64
- 應用場景
- Clang Attributes
- Clang警告處理
- 預處理
- Clang插件開發
- 總結
簡介
拖更很久了,今天水文一篇。簡單介紹下iOS底層編譯的相關知識,幫助我們充分理解了iOS編譯的過程,相信會對我們后續的開發有一定幫助。
源碼到可執行文件流程
首先看一下iOS代碼是如何從源碼變成可執行文件的,有助于我們了解程序從編譯到運行的全流程
編譯器
編譯器是將編程語言轉換為目標語言的程序,大多數編譯器由兩部分組成:前端和后端。
- 前端負責詞法分析,語法分析,生成中間代碼;
- 后端以中間代碼作為輸入,進行行架構無關的代碼優化,接著針對不同架構生成不同的機器碼。
前后端依賴統一格式的中間代碼(IR),使得前后端可以獨立的變化。新增一門語言只需要修改前端,而新增一個CPU架構只需要修改后端即可。
Objective C/C/C++使用的編譯器前端是clang,swift是swift,后端都是LLVM。
LLVM是一個模塊化和可重用的編譯器和工具鏈技術的集合,Clang 是 LLVM 的子項目,是 C,C++ 和 Objective-C 編譯器,目的是提供驚人的快速編譯,比 GCC 快3倍,
LLVM 還可以提供一種代碼編寫良好的中間表示 IR,這意味著它可以作為多種語言的后端,這樣就能夠提供語言無關的優化同時還能夠方便的針對多種 CPU 的代碼生成。
編譯流程
Objective-C的編譯器前端是Clang,誕生之初是為了替代GCC,提供更快的編譯速度。我們可以通過下面這張圖來了解Clang編譯的大致流程:
<br> (二維碼自動識別)
下面我們通過clang命令來具體分析下源碼編譯的流程:
首先在命令行里輸入
clang -ccc-print-phases main.m可以看到源文件編譯需要的幾個不同的階段
? clang -ccc-print-phases main.m 0: input, "main.m", objective-c 1: preprocessor, {0}, objective-c-cpp-output //預編譯 2: compiler, {1}, ir //編譯成中間代碼ir 3: backend, {2}, assembler //生成匯編 4: assembler, {3}, object //生成目標文件.O 5: linker, {4}, image //鏈接成可執行文件 6: bind-arch, "x86_64", {5}, image接下來我們新建一個main.m并詳細來看下每個步驟分別做了什么
main.m #include <stdio.h>int main() {printf("hello worldn");return 0; }預處理(preprocessor)
我們用下面的命令來查看clang預處理的結果:
clang -E main.m注:如果main.m中用到了UIKit等類,可以在命令后添加-sysroot參數,記得將sdk換成你本機的版本,后續命令解決方法相同。如下所示:clang -E main.m -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk
可以看到預處理后的文件行數有很多,在最后可以找到main函數
# 13 "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk/System/Library/Frameworks/UIKit.framework/Headers/ShareSheet.h" 2 3 # 17 "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk/System/Library/Frameworks/UIKit.framework/Headers/UIKit.h" 2 3 # 10 "main.m" 2 # 1 "./AppDelegate.h" 1 # 11 "./AppDelegate.h" @interface AppDelegate : UIResponder <UIApplicationDelegate>@property (strong, nonatomic) UIWindow *window;@end # 11 "main.m" 2int main(int argc, char * argv[]) {@autoreleasepool {return UIApplicationMain(argc, argv, ((void *)0), NSStringFromClass([AppDelegate class]));} }預處理會替進行頭文件引入(遞歸操作),宏替換#define,注釋處理,條件編譯(#ifdef),#pargma處理等操作。比如#include "stdio.h"就是告訴預處理器將這一行替換成頭文件stdio.h中的內容,這個過程是遞歸的:因為stdio.h也有可能包含其頭文件。
詞法分析(lexical anaysis)
預處理完成后就會進行詞法分析,這里會把代碼切成一個個 Token,比如大小括號,等于號還有字符串等。
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m語法分析(semantic analysis)
語法分析會校驗語法的正確性,然后將所有的節點組成抽象語法樹AST。有了抽象語法樹,clang就可以對這個樹進行分析,找出代碼中的錯誤。比如類型不匹配,亦或Objective C中向target發送了一個未實現的消息。
業內對Clang自定義插件或者開發靜態檢測插件都是基于AST語法樹來分析。相關知識后續會學到。AST是開發者編寫clang插件主要交互的數據結構,clang也提供很多API去讀取AST。更多細節:Introduction to the Clang AST。clang -fmodules -fsyntax-only -Xclang -ast-dump main.m在輸出里可以看到相關的AST結果,如下圖:
CodeGen
CodeGen 會負責將語法樹自頂向下遍歷逐步翻譯成 LLVM IR,IR 是編譯過程的前端的輸出,也是后端的輸入。
Objective C代碼也在這一步會進行runtime的橋接:property合成,ARC處理等。
查看main.ll的內容如下:
... ; Function Attrs: noinline optnone ssp uwtable define i32 @main(i32, i8**) #0 {%3 = alloca i32, align 4%4 = alloca i32, align 4%5 = alloca i8**, align 8store i32 0, i32* %3, align 4store i32 %0, i32* %4, align 4store i8** %1, i8*** %5, align 8%6 = call i8* @llvm.objc.autoreleasePoolPush() #1%7 = load i32, i32* %4, align 4%8 = load i8**, i8*** %5, align 8%9 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8%10 = bitcast %struct._class_t* %9 to i8*%11 = call i8* @objc_opt_class(i8* %10)%12 = call %0* @NSStringFromClass(i8* %11)%13 = bitcast %0* %12 to i8*%14 = notail call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %13) #1%15 = bitcast i8* %14 to %0*%16 = call i32 @UIApplicationMain(i32 %7, i8** %8, %0* null, %0* %15)store i32 %16, i32* %3, align 4%17 = bitcast %0* %15 to i8*call void @llvm.objc.release(i8* %17) #1, !clang.imprecise_release !10call void @llvm.objc.autoreleasePoolPop(i8* %6)%18 = load i32, i32* %3, align 4ret i32 %18 }; Function Attrs: nounwind ...如果在項目配置中開啟了 bitcode, 蘋果還會做進一步的優化,有新的后端架構還是可以用這份優化過的 bitcode 去生成。
clang -emit-llvm -c main.m -o main.bc生成匯編代碼
clang -S -fobjc-arc main.m -o main.s生成目標文件
匯編器以匯編代碼作為輸入,將匯編代碼轉換為機器代碼,最后輸出目標文件(object file)
clang -fmodules -c main.m -o main.o接下來我們用nm命令,查看下main.o中的符號
? BuildTest nm -nm main.o(undefined) external _printf 0000000000000000 (__TEXT,__text) external _main這里可以看到_printf是一個是undefined external的。undefined表示在當前文件暫時找不到符號_printf,而external表示這個符號是外部可以訪問的,對應表示文件私有的符號是non-external。
生成可執行文件
鏈接器可以把編譯產生的.o文件和(dylib,a,tbd)文件,生成一個mach-o文件
clang main.o -o main接著在命令行執行./main,可以看到輸出了結果:hello world。
最后我們用nm命令來分析下可執行文件的符號表:
可以看到_printf仍然是undefined,但是后面多了一些信息:from libSystem,表示這個符號來自于libSystem,會在運行時動態綁定。
以上就是Clang編譯源文件的完整流程了。
Xcode中查看Clang編譯.m文件信息
如果你想在 Xcode 中查看,可以通過 Show the report navigator 里對應 target 的 build 中查看每個 .m 文件的 clang 編譯信息,如下圖:
隨便找一個.m文件編譯信息,可以看到Xcode會首先對任務進行描述:
CompileC /Users/chenaibin/Library/Developer/Xcode/DerivedData/PodIntegrationDemo-achbuytjuwbatqbzvlwflifarxwa/Build/Intermediates.noindex/Pods.build/Debug-iphonesimulator/podLibB.build/Objects-normal/x86_64/podClsB.o /Users/chenaibin/Work/DiDi/iOSDemo/BuildErrorDemo/podLibB/Classes/podClsB.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler (in target 'podLibB' from project 'Pods')接下來對會更新工作路徑,同時設置 PATH
cd /Users/chenaibin/Work/DiDi/iOSDemo/BuildErrorDemo/PodIntegrationDemo/Pods export LANG=en_US.US-ASCII export PATH="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"接下來就是實際的編譯命令
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x objective-c -target x86_64-apple-ios9.0-simulator -fmessage-length=0 -fobjc-arc… -Wno-missing-field-initializers ... -DDEBUG=1 ... -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk -iquote ... -I... -F...-c /.../podClsB.m -o /.../podClsB.oclang 用到的命令參數如下:
-x 編譯語言比如objective-c -arch 編譯的架構,比如arm64 -f 以-f開頭的。 -W 以-W開頭的,可以通過這些定制編譯警告 -D 以-D開頭的,指的是預編譯宏,通過這些宏可以實現條件編譯 -iPhoneSimulator13.0.sdk 編譯采用的iOS SDK版本 -I 把編譯信息寫入指定的輔助文件 -F 需要的Framework -c 標識符指明需要運行預處理器,語法分析,類型檢查,LLVM生成優化以及匯編代碼生成.o文件 -o 編譯結果Xcode常見編譯報錯分析
1. duplicate symbols報錯
第一個常見的編譯報錯原因就是duplicate symbols,如下圖就是因為我們鏈接后的可執行文件存在了重復的類導致的。
注:由于我們工程是由CocoaPods構建的,在xcconfig中OTHER_LINK_FLAG都會被默認設置成$(inherited) -ObjC ......,這會導致工程配置里Other Linker Flags會帶上 -ObjC標記,如果我們手動刪除了-ObjC,就會發現在編譯時不會有duplicate symbols的錯誤了。但是運行的時候可能會出現unrecognized selector sent to class XXX的錯誤,這是由于靜態庫中的分類并沒被鏈接器鏈接進可執行文件中。
-ObjC會把靜態庫中所有的類和分類都鏈接進可執行文件,所以會出現duplicate symbols的錯誤。下面是官方描述:
This flag causes the linker to load every object file in the library that defines an Objective-C class or category. While this option will typically result in a larger executable (due to additional object code loaded into the application), it will allow the successful creation of effective Objective-C static libraries that contain categories on existing classes.2. symbol(s) not found for architecture x86_64/arm64
第二個常見報錯是在某個架構下找不到相關符號,這是因為引用的某個靜態庫并沒有包含當前工程制式下的架構類型,解決方案是將靜態庫.a文件合并x86_64/arm64等架構為fat file,再集成到工程里使用。
報錯原因如下圖:
提示:遇到這種情況時,有時候多次pod update也不能解決報錯原因。這是因為你本地緩存了有問題的靜態庫文件,可在以下目錄下找到相關類庫并刪除,再執行pod install下載fix后的靜態庫文件。CocoaPods官方緩存目錄:~/Library/Caches/CocoaPods/Pods這個錯誤還有另外一種情況,當同一個pod在多個不同的端集成時可能會遇到。報錯信息大致如下:
問題原因:在ProjectA中集成了podA和podB,podA使用了#if __has_include("podB中的cls.h")集成了podB中的類;當切換到ProjectB時,只會依賴podA一個庫,這個時候編譯就會上圖中的錯誤。
解決方案:在ProjectB中將podA以源碼重新編譯一遍即可。
應用場景
Clang Attributes
在平時開發中,我們經常會遇到頭文件里有__attribute__的用法,它是一個高級的的編譯器指令,它允許開發者指定更更多的編譯檢查和一些高級的編譯期優化。
__attribute__ 語法格式為:__attribute__ ((attribute-list)) 放在聲明分號“;”前面。
比如,在三方庫中最常見的,聲明一個屬性或者方法在當前版本棄用了
@property (strong,nonatomic)CLASSNAME * property __deprecated;
下面是 iOS開發中常見的幾個 __attribute__ 用法:
//棄用API,用作API更新 #define __deprecated __attribute__((deprecated))//帶描述信息的棄用 #define __deprecated_msg(_msg) __attribute__((deprecated(_msg)))//遇到__unavailable的變量/方法,編譯器直接拋出Error #define __unavailable __attribute__((unavailable))//告訴編譯器,即使這個變量/方法 沒被使用,也不要拋出警告 #define __unused __attribute__((unused))//和__unused相反 #define __used __attribute__((used))//如果不使用方法的返回值,進行警告 #define __result_use_check __attribute__((__warn_unused_result__))//OC方法在Swift中不可用 #define __swift_unavailable(_msg) __attribute__((__availability__(swift, unavailable, message=_msg)))Clang警告處理
當我們在XCode中屏蔽部分Warning信息時,可以使用下面的內容來解決。通過clang diagnostic push/pop來控制代碼塊的編譯選項。
#pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" ///代碼 #pragma clang diagnostic pop預處理
預處理可以讓我們讓我們自定義編譯器變量,實現條件編譯。 比如我們常用的DEBUG宏:
#ifdef DEBUG //... #else //... #endif我們可以在XCode的Target中選中Build Setting選項,搜索proprecess,即可看到定義好的預處理宏。
目前iOS基本都是用CocoaPods來管理工程,我們也可以在每個Pod的podspec文件中配置預編譯宏,CocoaPods會在構建工程時將這些信息寫到Pod的xcconfig文件里。
# Pod.podspec示例 s.subspec 'YourSubSpec' do | ss |ss.source_files = 'Pod/Classes/**/*'ss.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) YOUR_CUSTOM_DEFINE=1' } end注意:podA定義的GCC_PREPROCESSOR_DEFINITIONS內容在podB中是不生效的!!!如果想解決這個問題,推薦podB中單獨定義一個subspec來配置預編譯宏的值,在外層工程里通過區分是否引入podB的subspec來實現該預編譯宏值的控制。
Clang插件開發
上面介紹到語法分析之后我們可以拿到抽象語法樹AST,接著就可以對這個樹進行分析,做靜態代碼分析或者無用代碼分析都可以,網上也有很多資料介紹這塊的研究。感興趣的可以搜索下或者看下 Introduction to the Clang AST
總結
以上內容主要介紹了下iOS編譯相關的知識,如有內容錯誤,歡迎指正。
推薦 :
作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:789143298 ,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!- ——點擊加入:iOS開發交流群
- BAT大廠面試題、獨家面試工具包,
- 資料免費領取,包括 數據結構、底層進階、圖形視覺、音視頻、架構設計、逆向安防、RxSwift、flutter,
原文作者:陳愛彬
總結
以上是生活随笔為你收集整理的devc++源文件未编译_iOS 编译知识小结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java实验册_Java实验报告册Jav
- 下一篇: 电脑bios从u盘启动设置u盘启动怎么办