日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

devc++源文件未编译_iOS 编译知识小结

發(fā)布時間:2024/9/27 c/c++ 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 devc++源文件未编译_iOS 编译知识小结 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
  • 簡介
  • 源碼到可執(zhí)行文件流程
  • 編譯器
  • 編譯流程
    • 預(yù)處理(preprocessor)
    • 詞法分析(lexical anaysis)
    • 語法分析(semantic analysis)
    • CodeGen
    • 生成匯編代碼
    • 生成目標(biāo)文件
    • 生成可執(zhí)行文件
  • Xcode中查看Clang編譯.m文件信息
  • Xcode常見編譯報錯分析
    • 1. duplicate symbols報錯
    • 2. symbol(s) not found for architecture x86_64/arm64
  • 應(yīng)用場景
    • Clang Attributes
    • Clang警告處理
    • 預(yù)處理
    • Clang插件開發(fā)
  • 總結(jié)

簡介

拖更很久了,今天水文一篇。簡單介紹下iOS底層編譯的相關(guān)知識,幫助我們充分理解了iOS編譯的過程,相信會對我們后續(xù)的開發(fā)有一定幫助。

源碼到可執(zhí)行文件流程

首先看一下iOS代碼是如何從源碼變成可執(zhí)行文件的,有助于我們了解程序從編譯到運行的全流程

  • 編譯器Clang會將源碼XXX.m編譯為目標(biāo)文件XXX.o
  • 鏈接器會將目標(biāo)文件鏈接打包進最終的可執(zhí)行文件Mach-O中
  • 點擊App ICON時,動態(tài)鏈接器dyld會加載可執(zhí)行文件以及依賴的動態(tài)庫,并最終執(zhí)行到main.m里,至此App啟動完成
  • 編譯器

    編譯器是將編程語言轉(zhuǎn)換為目標(biāo)語言的程序,大多數(shù)編譯器由兩部分組成:前端和后端。

    • 前端負(fù)責(zé)詞法分析,語法分析,生成中間代碼;
    • 后端以中間代碼作為輸入,進行行架構(gòu)無關(guān)的代碼優(yōu)化,接著針對不同架構(gòu)生成不同的機器碼。

    前后端依賴統(tǒng)一格式的中間代碼(IR),使得前后端可以獨立的變化。新增一門語言只需要修改前端,而新增一個CPU架構(gòu)只需要修改后端即可。

    Objective C/C/C++使用的編譯器前端是clang,swift是swift,后端都是LLVM。

    LLVM是一個模塊化和可重用的編譯器和工具鏈技術(shù)的集合,Clang 是 LLVM 的子項目,是 C,C++ 和 Objective-C 編譯器,目的是提供驚人的快速編譯,比 GCC 快3倍,

    LLVM 還可以提供一種代碼編寫良好的中間表示 IR,這意味著它可以作為多種語言的后端,這樣就能夠提供語言無關(guān)的優(yōu)化同時還能夠方便的針對多種 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 //預(yù)編譯 2: compiler, {1}, ir //編譯成中間代碼ir 3: backend, {2}, assembler //生成匯編 4: assembler, {3}, object //生成目標(biāo)文件.O 5: linker, {4}, image //鏈接成可執(zhí)行文件 6: bind-arch, "x86_64", {5}, image

    接下來我們新建一個main.m并詳細(xì)來看下每個步驟分別做了什么

    main.m #include <stdio.h>int main() {printf("hello worldn");return 0; }

    預(yù)處理(preprocessor)

    我們用下面的命令來查看clang預(yù)處理的結(jié)果:

    clang -E main.m注:如果main.m中用到了UIKit等類,可以在命令后添加-sysroot參數(shù),記得將sdk換成你本機的版本,后續(xù)命令解決方法相同。如下所示:
    clang -E main.m -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk

    可以看到預(yù)處理后的文件行數(shù)有很多,在最后可以找到main函數(shù)

    # 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]));} }

    預(yù)處理會替進行頭文件引入(遞歸操作),宏替換#define,注釋處理,條件編譯(#ifdef),#pargma處理等操作。比如#include "stdio.h"就是告訴預(yù)處理器將這一行替換成頭文件stdio.h中的內(nèi)容,這個過程是遞歸的:因為stdio.h也有可能包含其頭文件。

    詞法分析(lexical anaysis)

    預(yù)處理完成后就會進行詞法分析,這里會把代碼切成一個個 Token,比如大小括號,等于號還有字符串等。

    clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

    語法分析(semantic analysis)

    語法分析會校驗語法的正確性,然后將所有的節(jié)點組成抽象語法樹AST。有了抽象語法樹,clang就可以對這個樹進行分析,找出代碼中的錯誤。比如類型不匹配,亦或Objective C中向target發(fā)送了一個未實現(xiàn)的消息。

    業(yè)內(nèi)對Clang自定義插件或者開發(fā)靜態(tài)檢測插件都是基于AST語法樹來分析。相關(guān)知識后續(xù)會學(xué)到。AST是開發(fā)者編寫clang插件主要交互的數(shù)據(jù)結(jié)構(gòu),clang也提供很多API去讀取AST。更多細(xì)節(jié):Introduction to the Clang AST。clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

    在輸出里可以看到相關(guān)的AST結(jié)果,如下圖:

    CodeGen

    CodeGen 會負(fù)責(zé)將語法樹自頂向下遍歷逐步翻譯成 LLVM IR,IR 是編譯過程的前端的輸出,也是后端的輸入。
    Objective C代碼也在這一步會進行runtime的橋接:property合成,ARC處理等。

    clang -S -fobjc-arc -emit-llvm main.m -o main.ll

    查看main.ll的內(nèi)容如下:

    ... ; 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, 蘋果還會做進一步的優(yōu)化,有新的后端架構(gòu)還是可以用這份優(yōu)化過的 bitcode 去生成。

    clang -emit-llvm -c main.m -o main.bc

    生成匯編代碼

    clang -S -fobjc-arc main.m -o main.s

    生成目標(biāo)文件

    匯編器以匯編代碼作為輸入,將匯編代碼轉(zhuǎn)換為機器代碼,最后輸出目標(biāo)文件(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表示在當(dāng)前文件暫時找不到符號_printf,而external表示這個符號是外部可以訪問的,對應(yīng)表示文件私有的符號是non-external。

    生成可執(zhí)行文件

    鏈接器可以把編譯產(chǎn)生的.o文件和(dylib,a,tbd)文件,生成一個mach-o文件

    clang main.o -o main

    接著在命令行執(zhí)行./main,可以看到輸出了結(jié)果:hello world。
    最后我們用nm命令來分析下可執(zhí)行文件的符號表:

    ? BuildTest nm -nm main(undefined) external _printf (from libSystem)(undefined) external dyld_stub_binder (from libSystem) 0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header 0000000100000f60 (__TEXT,__text) external _main 0000000100002008 (__DATA,__data) non-external __dyld_private

    可以看到_printf仍然是undefined,但是后面多了一些信息:from libSystem,表示這個符號來自于libSystem,會在運行時動態(tài)綁定。

    以上就是Clang編譯源文件的完整流程了。

    Xcode中查看Clang編譯.m文件信息

    如果你想在 Xcode 中查看,可以通過 Show the report navigator 里對應(yīng) target 的 build 中查看每個 .m 文件的 clang 編譯信息,如下圖:

    隨便找一個.m文件編譯信息,可以看到Xcode會首先對任務(wù)進行描述:

    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')

    接下來對會更新工作路徑,同時設(shè)置 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.o

    clang 用到的命令參數(shù)如下:

    -x 編譯語言比如objective-c -arch 編譯的架構(gòu),比如arm64 -f 以-f開頭的。 -W 以-W開頭的,可以通過這些定制編譯警告 -D 以-D開頭的,指的是預(yù)編譯宏,通過這些宏可以實現(xiàn)條件編譯 -iPhoneSimulator13.0.sdk 編譯采用的iOS SDK版本 -I 把編譯信息寫入指定的輔助文件 -F 需要的Framework -c 標(biāo)識符指明需要運行預(yù)處理器,語法分析,類型檢查,LLVM生成優(yōu)化以及匯編代碼生成.o文件 -o 編譯結(jié)果

    Xcode常見編譯報錯分析

    1. duplicate symbols報錯

    第一個常見的編譯報錯原因就是duplicate symbols,如下圖就是因為我們鏈接后的可執(zhí)行文件存在了重復(fù)的類導(dǎo)致的。

    注:由于我們工程是由CocoaPods構(gòu)建的,在xcconfig中OTHER_LINK_FLAG都會被默認(rèn)設(shè)置成$(inherited) -ObjC ......,這會導(dǎo)致工程配置里Other Linker Flags會帶上 -ObjC標(biāo)記,如果我們手動刪除了-ObjC,就會發(fā)現(xiàn)在編譯時不會有duplicate symbols的錯誤了。但是運行的時候可能會出現(xiàn)unrecognized selector sent to class XXX的錯誤,這是由于靜態(tài)庫中的分類并沒被鏈接器鏈接進可執(zhí)行文件中。

    -ObjC會把靜態(tài)庫中所有的類和分類都鏈接進可執(zhí)行文件,所以會出現(xiàn)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

    第二個常見報錯是在某個架構(gòu)下找不到相關(guān)符號,這是因為引用的某個靜態(tài)庫并沒有包含當(dāng)前工程制式下的架構(gòu)類型,解決方案是將靜態(tài)庫.a文件合并x86_64/arm64等架構(gòu)為fat file,再集成到工程里使用。

    報錯原因如下圖:

    提示:遇到這種情況時,有時候多次pod update也不能解決報錯原因。這是因為你本地緩存了有問題的靜態(tài)庫文件,可在以下目錄下找到相關(guān)類庫并刪除,再執(zhí)行pod install下載fix后的靜態(tài)庫文件。CocoaPods官方緩存目錄:~/Library/Caches/CocoaPods/Pods

    這個錯誤還有另外一種情況,當(dāng)同一個pod在多個不同的端集成時可能會遇到。報錯信息大致如下:

    問題原因:在ProjectA中集成了podA和podB,podA使用了#if __has_include("podB中的cls.h")集成了podB中的類;當(dāng)切換到ProjectB時,只會依賴podA一個庫,這個時候編譯就會上圖中的錯誤。

    解決方案:在ProjectB中將podA以源碼重新編譯一遍即可。

    應(yīng)用場景

    Clang Attributes

    在平時開發(fā)中,我們經(jīng)常會遇到頭文件里有__attribute__的用法,它是一個高級的的編譯器指令,它允許開發(fā)者指定更更多的編譯檢查和一些高級的編譯期優(yōu)化。

    __attribute__ 語法格式為:__attribute__ ((attribute-list)) 放在聲明分號“;”前面。

    比如,在三方庫中最常見的,聲明一個屬性或者方法在當(dāng)前版本棄用了

    @property (strong,nonatomic)CLASSNAME * property __deprecated;

    下面是 iOS開發(fā)中常見的幾個 __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警告處理

    當(dāng)我們在XCode中屏蔽部分Warning信息時,可以使用下面的內(nèi)容來解決。通過clang diagnostic push/pop來控制代碼塊的編譯選項。

    #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" ///代碼 #pragma clang diagnostic pop

    預(yù)處理

    預(yù)處理可以讓我們讓我們自定義編譯器變量,實現(xiàn)條件編譯。 比如我們常用的DEBUG宏:

    #ifdef DEBUG //... #else //... #endif

    我們可以在XCode的Target中選中Build Setting選項,搜索proprecess,即可看到定義好的預(yù)處理宏。

    目前iOS基本都是用CocoaPods來管理工程,我們也可以在每個Pod的podspec文件中配置預(yù)編譯宏,CocoaPods會在構(gòu)建工程時將這些信息寫到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內(nèi)容在podB中是不生效的!!!
    如果想解決這個問題,推薦podB中單獨定義一個subspec來配置預(yù)編譯宏的值,在外層工程里通過區(qū)分是否引入podB的subspec來實現(xiàn)該預(yù)編譯宏值的控制。

    Clang插件開發(fā)

    上面介紹到語法分析之后我們可以拿到抽象語法樹AST,接著就可以對這個樹進行分析,做靜態(tài)代碼分析或者無用代碼分析都可以,網(wǎng)上也有很多資料介紹這塊的研究。感興趣的可以搜索下或者看下 Introduction to the Clang AST

    總結(jié)

    以上內(nèi)容主要介紹了下iOS編譯相關(guān)的知識,如有內(nèi)容錯誤,歡迎指正。

    推薦 :

    作為一個開發(fā)者,有一個學(xué)習(xí)的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:789143298 ,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經(jīng)驗,討論技術(shù), 大家一起交流學(xué)習(xí)成長!
    • ——點擊加入:iOS開發(fā)交流群
    • BAT大廠面試題、獨家面試工具包,
    • 資料免費領(lǐng)取,包括 數(shù)據(jù)結(jié)構(gòu)、底層進階、圖形視覺、音視頻、架構(gòu)設(shè)計、逆向安防、RxSwift、flutter,

    原文作者:陳愛彬

    總結(jié)

    以上是生活随笔為你收集整理的devc++源文件未编译_iOS 编译知识小结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。