ios上编译c语言的app,iOS App编译流程
三大編譯器詳解GCC / Clang / LLVM
傳統的編譯器通常分為三個部分,前端(frontEnd),優化器(Optimizer)和后端(backEnd)。在編譯過程中,前端主要負責詞法和語法分析,將源代碼轉化為抽象語法樹(AST)生成中間代碼;優化器則是在前端的基礎上,對得到的中間代碼進行優化,使代碼更加高效;后端則是將已經優化的中間代碼根據不同架構的平臺轉化為各自平臺的機器代碼。
GCC
GCC(GNU Compiler Collection,GNU編譯器套裝),是一套由 GNU 開發的編程語言編譯器。GCC 原名為 GNU C 語言編譯器,因為它原本只能處理 C語言。GCC 快速演進,變得可處理 C++、Fortran、Pascal、Objective-C、Java, 以及 Ada 等他語言。
LLVM
LLVM是一個完整的編譯器(compiler)框架系統,以C++編寫而成,用于優化以任意程序語言編寫的程序的編譯時間(compile-time)、鏈接時間(link-time)、運行時間(run-time)以及空閑時間(idle-time),對開發者保持開放,并兼容已有腳本。
在理解LLVM時,我們可以認為它包括了一個狹義的LLVM和一個廣義的LLVM。廣義的LLVM其實就是指整個LLVM編譯器架構,包括了前端、后端、優化器、眾多的庫函數以及其他的模塊;而狹義的LLVM其實就是聚焦于編譯器后端功能(代碼生成、代碼優化、JIT等)的一系列模塊和庫。
Clang
Clang是LLVM編譯系統的前端,是GCC的替代品,其可以看成是LLVM的子集,相比于GCC編譯器Clang功能更加強大,其速度快;占用內存小;診斷信息可讀性強;兼容性好;Clang有靜態分析而GCC沒有;Clang使用BSD許可證,GCC使用GPL許可證。
下面用一張圖來表示Clang與LLVM之間的關系:
Snip20210112_43.png
從圖中可以看出Clang其實大致上可以看成是LLVM編譯器架構的前端,主要處理一些和具體機器無關的針對語言的分析操作;編譯器的優化器部分和后端部分其實就是我們之前談到的LLVM后端(狹義的LLVM);而整體的Compiler架構就是LLVM架構。
iOSApp編譯使用的就是LLVM架構 即Clang+LLVM.
iOS底層編譯的流程
下面通過具體的實例代碼來闡述iOS底層編譯的過程.
Xcode的默認編譯器是 clang,clang首先會對 Objective-C 代碼做預處理,分析檢查,然后將其轉換為低級的類匯編代碼.
預處理(preprocessor)
C文件代碼如下所示:
#import
#define DEBUG 1
int main(int argc, const char * argv[]) {
@autoreleasepool {
//insert code ...
#ifdef DEBUG
printf("hello debug\n");
#else
printf("hello world\n");
#endif
NSLog(@"Hello, World!");
}
return 0;
}
使用終端命令:
xcrun clang -E main.m
終端中生成的代碼為:
int main(int argc, const char * argv[]) {
@autoreleasepool {
printf("hello debug\n");
NSLog(@"Hello, World!");
}
return 0;
}
可以看到,編譯在預處理的時候,注釋被刪除,條件編譯被處理。
預處理完成之后的具體流程如下圖所示:
Snip20210112_44.png
詞法分析
詞法分析器讀入源文件的字符流,然后將他們組織成有意義的詞素(lexeme)序列,對于每個詞素,詞法分析器會生成對應的詞法單元(token)作為輸出。
前端代碼
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
int b = 20;
int c = a + b;
NSLog(@" c = %d",c);
}
return 0;
}
使用終端命令:
xcrun clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
終端中生成的代碼為:
annot_module_include '#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
int b' Loc=<9:1>9:1>
int 'int' [StartOfLine] Loc=<11:1>11:1>
identifier 'main' [LeadingSpace] Loc=<11:5>11:5>
l_paren '(' Loc=<11:9>11:9>
int 'int' Loc=<11:10>11:10>
identifier 'argc' [LeadingSpace] Loc=<11:14>11:14>
comma ',' Loc=<11:18>11:18>
const 'const' [LeadingSpace] Loc=<11:20>11:20>
char 'char' [LeadingSpace] Loc=<11:26>11:26>
star '*' [LeadingSpace] Loc=<11:31>11:31>
identifier 'argv' [LeadingSpace] Loc=<11:33>11:33>
l_square '[' Loc=<11:37>11:37>
r_square ']' Loc=<11:38>11:38>
r_paren ')' Loc=<11:39>11:39>
l_brace '{' [LeadingSpace] Loc=<11:41>11:41>
at '@' [StartOfLine] [LeadingSpace] Loc=<12:5>12:5>
identifier 'autoreleasepool' Loc=<12:6>12:6>
l_brace '{' [LeadingSpace] Loc=<12:22>12:22>
int 'int' [StartOfLine] [LeadingSpace] Loc=<13:9>13:9>
identifier 'a' [LeadingSpace] Loc=<13:13>13:13>
equal '=' [LeadingSpace] Loc=<13:15>13:15>
numeric_constant '10' [LeadingSpace] Loc=<13:17>13:17>
semi ';' Loc=<13:19>13:19>
int 'int' [StartOfLine] [LeadingSpace] Loc=<14:9>14:9>
identifier 'b' [LeadingSpace] Loc=<14:13>14:13>
equal '=' [LeadingSpace] Loc=<14:15>14:15>
numeric_constant '20' [LeadingSpace] Loc=<14:17>14:17>
semi ';' Loc=<14:19>14:19>
int 'int' [StartOfLine] [LeadingSpace] Loc=<15:9>15:9>
identifier 'c' [LeadingSpace] Loc=<15:13>15:13>
equal '=' [LeadingSpace] Loc=<15:15>15:15>
identifier 'a' [LeadingSpace] Loc=<15:17>15:17>
plus '+' [LeadingSpace] Loc=<15:19>15:19>
identifier 'b' [LeadingSpace] Loc=<15:21>15:21>
semi ';' Loc=<15:22>15:22>
identifier 'NSLog' [StartOfLine] [LeadingSpace] Loc=<16:9>16:9>
l_paren '(' Loc=<16:14>16:14>
at '@' Loc=<16:15>16:15>
string_literal '" c = %d"' Loc=<16:16>16:16>
comma ',' Loc=<16:25>16:25>
identifier 'c' Loc=<16:26>16:26>
r_paren ')' Loc=<16:27>16:27>
semi ';' Loc=<16:28>16:28>
r_brace '}' [StartOfLine] [LeadingSpace] Loc=<17:5>17:5>
return 'return' [StartOfLine] [LeadingSpace] Loc=<18:5>18:5>
numeric_constant '0' [LeadingSpace] Loc=<18:12>18:12>
semi ';' Loc=<18:13>18:13>
r_brace '}' [StartOfLine] Loc=<19:1>19:1>
eof '' Loc=<20:1>20:1>
可以看出詞法分析多了Loc來記錄位置。
語法分析
詞法分析的Token流會被解析成一顆抽象語法樹(abstract syntax tree - AST)。
使用終端命令:
clang -Xclang -ast-dump -fsyntax-only main.m
上述的代碼生成抽象語法樹如下所示:
Snip20210112_45.png
靜態分析
把源碼轉化為抽象語法樹之后,編譯器就可以對這個樹進行分析處理。靜態分析會對代碼進行錯誤檢查,如出現方法被調用但是未定義、定義但是未使用的變量等,以此提高代碼質量。當然,還可以通過使用 Xcode 自帶的靜態分析工具(Product -> Analyze)
中間代碼生成和優化 生成main.ll文件
使用終端命令:
clang -O3 -S -emit-llvm main.m -o main.ll
會在本地生成一個main.ll文件,使用Xcode打開其內容如下:
; ModuleID = 'main.m'
source_filename = "main.m"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.15.0"
%struct.__NSConstantString_tag = type { i32*, i32, i8*, i64 }
@__CFConstantStringClassReference = external global [0 x i32]
@.str = private unnamed_addr constant [8 x i8] c" c = %d\00", section "__TEXT,__cstring,cstring_literals", align 1
@_unnamed_cfstring_ = private global %struct.__NSConstantString_tag { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str, i32 0, i32 0), i64 7 }, section "__DATA,__cfstring", align 8
; Function Attrs: ssp uwtable
define i32 @main(i32, i8** nocapture readnone) local_unnamed_addr #0 {
%3 = tail call i8* @llvm.objc.autoreleasePoolPush() #1
notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*), i32 30)
tail call void @llvm.objc.autoreleasePoolPop(i8* %3)
ret i32 0
}
; Function Attrs: nounwind
declare i8* @llvm.objc.autoreleasePoolPush() #1
declare void @NSLog(i8*, ...) local_unnamed_addr #2
; Function Attrs: nounwind
declare void @llvm.objc.autoreleasePoolPop(i8*) #1
attributes #0 = { ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "darwin-stkchk-strong-link" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "probe-stack"="___chkstk_darwin" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { nounwind }
attributes #2 = { "correctly-rounded-divide-sqrt-fp-math"="false" "darwin-stkchk-strong-link" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "probe-stack"="___chkstk_darwin" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7}
!llvm.ident = !{!8}
!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 10, i32 15]}
!1 = !{i32 1, !"Objective-C Version", i32 2}
!2 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!3 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
!4 = !{i32 4, !"Objective-C Garbage Collection", i32 0}
!5 = !{i32 1, !"Objective-C Class Properties", i32 64}
!6 = !{i32 1, !"wchar_size", i32 4}
!7 = !{i32 7, !"PIC Level", i32 2}
!8 = !{!"Apple clang version 11.0.0 (clang-1100.0.33.17)"}
接下來 LLVM 會對代碼進行編譯優化,例如針對全局變量優化、循環優化、尾遞歸優化等,最后輸出匯編代碼。
生成匯編文件 main.s
使用終端命令:
clang -S -fobjc-arc main.m -o main.s
匯編代碼如下:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $48, %rsp
movl $0, -4(%rbp)
movl %edi, -8(%rbp)
movq %rsi, -16(%rbp)
callq _objc_autoreleasePoolPush
leaq L__unnamed_cfstring_(%rip), %rsi
movl $10, -20(%rbp)
movl $20, -24(%rbp)
movl -20(%rbp), %edi
addl -24(%rbp), %edi
movl %edi, -28(%rbp)
movl -28(%rbp), %edi
movl %edi, -32(%rbp) ## 4-byte Spill
movq %rsi, %rdi
movl -32(%rbp), %esi ## 4-byte Reload
movq %rax, -40(%rbp) ## 8-byte Spill
movb $0, %al
callq _NSLog
movq -40(%rbp), %rdi ## 8-byte Reload
callq _objc_autoreleasePoolPop
xorl %eax, %eax
addq $48, %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz " c = %d"
.section __DATA,__cfstring
.p2align 3 ## @_unnamed_cfstring_
L__unnamed_cfstring_:
.quad ___CFConstantStringClassReference
.long 1992 ## 0x7c8
.space 4
.quad L_.str
.quad 7 ## 0x7
.section __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
.long 0
.long 64
.subsections_via_symbols
生成機器代碼,最后輸出目標文件(二進制文件) main.o
使用終端命令:
xcrun clang -fmodules -c main.m -o main.o
鏈接器 鏈接linker 生成一個可執行的mach-o 文件
鏈接器把編譯生成的所有 .o 文件和(dylib、a、tbd)文件結合,生成一個 mach-o 文件(可執行文件).
使用終端命令:
xcrun clang main.o -o main
在編譯時,鏈接器的主要工作任務
符號綁定:完成變量名與其地址,函數名與其地址之間的綁定;
Mach-O文件中的主要內容是數據和代碼,數據就是全局變量,代碼就是函數, 全局變量和函數都存儲在指定的位置, 計算機指令通過符號去操作數據和調用函數,首先必須要進行符號的綁定,才能根據符號去操作數據和調用函數.
靜態庫的鏈接:將靜態庫文件直接打包進入Mach-O文件中.
生成的多個Mach-O文件最終合成一個Mach-O文件;項目中各個文件之間的函數存在相互調用,也就是說存在相互依賴,單個的Mach-O文件是無法執行成功的,因為當前文件中需要調用其他文件中的函數方法,最終由于找不到對應的函數而終止執行,所以我們需要把所有的Mach-O文件合并,最終生成一個可執行的Mach-O文件.
對于Mach-O文件中的動態庫,在編譯期只是對其進行引用并沒有加載,等到了App運行時,dyld會對Mach-O文件中所有動態庫的引用進行掃描,逐一加載鏈接,但不會打包進Mach-O文件中,這是與靜態庫最大的區別。
編譯多個文件 -- 實戰演練
首先創建一個C語言工程如下所示:
Snip20210118_13.png
在終端依次輸入:
xcrun clang -c main.m
xcrun clang -c YYPerson.m
在本地會生成兩個目標文件 main.o 與YYPerson.o
Snip20210118_14.png
將多個編譯之后的目標文件通過鏈接器進行鏈接,生成a.out可執行文件
在終端中輸入:
xcrun clang main.o YYPerson.o -Wl,xcrun —show-sdk-path/System/Library/Frameworks/Foundation.framework/Foundation
Snip20210118_15.png
在終端中輸入以下命令進行查看:
xcrun nm -nm a.out
Snip20210118_16.png
undefined 符號表示的是該文件類未定義,所以在目標文件和 Foundation framework 動態庫做鏈接處理時,鏈接器會嘗試解析所有的 undefined 符號。
dylib 這種格式,表示是動態鏈接的,編譯的時候不會被編譯到執行文件中,在程序執行的時候才 link,這樣就不用算到包大小里,而且不更新執行程序就能夠更新庫。
總結
以上是生活随笔為你收集整理的ios上编译c语言的app,iOS App编译流程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言 变量 定义 使用,C语言为什么要
- 下一篇: c语言仓库管理系统链表,仓库管理系统 C