全局符號 和本地符號
我們先看一段代碼
查看Mach-O的符號
通過如下命令
objdump --macho --syms patch
- 可以看到main.m 生成的mach-O中的符號信息, 全局符號為g
- static 修飾的變量 為 l
所以 l :就是 local 本地的意思 ,g 就是global 全局的意思
按照功能劃分
Type | 說明 |
---|---|
f | File |
F | Function |
O | Data |
d | Debug |
*ABS* | Absolute |
*COM* | Common |
*UND* | ? |
調試符號
可以通過 連接器的參數(shù) 再鏈接的時候 不把調試符號放入我們可執(zhí)行文件
OTHER_LDFLAGS=$(inherited) -Xlinker -S
編譯一下 再次查看符號表
- 此時我們發(fā)現(xiàn) 調試符號已經被我們脫掉了,更能清晰的對照原始代碼來查看
- 調試符號:當一個文件通過匯編器生成.o的時候 會DWARF 格式的調試信息 它被放在了專門的mach-O中的一個 __DWARF段 當去連接的時候它會把整個段變成符號放在符號表中,當鏈接的時候所有的符號都是放在符號表中的。
DWARF文件 在.o的時候是一個段 ,此時跟符號表并沒有產生關系,當變成可執(zhí)行文件的時候在鏈接的時候 這個段被干掉最終變成了符號 最終 放在了 可執(zhí)行文件中exec
visibility
可通過visibility屬性,控制文件導出符號,限制符號可見性。- hidden : 用它定義分符號將不被導出
- default: 用它定義的符號將被導出
- protected: 受保護的
__attribute__ : 可以把編譯器支持的一些參數(shù)傳遞給編譯器(如上面是傳入的是控制符號的可見性,那最好的隱藏符號是什么?就是是把全局符號變成本地符號)。
屏幕快照 2021-02-21 下午8.31.59.png
拓展
面試題:全局變量 和靜態(tài)變量最大區(qū)別是什么?
作用域不一樣,本質的區(qū)別就是可見性。
如果全局變量 那么會對整個項目可見
如是靜態(tài)變量 則會變成 local-
關于 一個同名 全局方法 本地也寫了一個同名方法 調用 是調用 本地的 還是 別的地方的。
屏幕快照 2021-02-21 下午9.16.13.png
屏幕快照 2021-02-21 下午9.35.16.png
直接說答案:此時編譯不會報錯,并且還會調用到
當我在本地寫一個實現(xiàn):
發(fā)現(xiàn)本地的被調用起來了
為什么 會調用起來呢 為啥不沖突 因為連接器默認采用二級命名空間 修改為一級就報錯了。
導入和導出符號
是導入 還是導出 相對于來說的,比如你調用了NSLog 那相對于Foundation 庫來說就是 導出,相對于你來說就是導入。
那導出符號一定是 全局符號了?從字面上是這樣的 那么我來看一下
查看mach-O的導出符號命令
objdump --macho --exports-trie 黑不溜秋的
- 所以我們要注意 當我們使用全局變量 全局符號的 時候 它默認會為導出符號,對外界暴露的,也就意味著 其他空間 可以使用這個符號。
那導出符號一定是全局符號嗎?按道理是的 ,但是我們可以通過連接器控制它。
動態(tài)庫 是在運行的時候加載,那就意味這它在編譯連接階段的時候提供符號就可以了。
間接符號表,保存著當前使用的其他動態(tài)庫的符號
查看間接符號表
objdump --macho --indirect-symbols 黑不溜秋的
好像只認識NSlog 但是它是Fundation 給我們提供的導出符號
從上面我們可以理解:
1、全局符號可以變成導出符號 ,那么變成導出符號之后 可以給外面使用
2、當我們處理符號,因為符號存在我們Mach-o中是占用一定體積的 。 那能處理能刪除間接符號嗎?那肯定必須的不能刪除啊,為什么?因為對于我們項目mach-o來說 間接符號表 里保存著當前使用著的動態(tài)庫的符號,而動態(tài)庫是運行的時候去加載。所以也就意味著 我們在編譯鏈接的時候只需要提供它所用到的符號就行,而這符號就存在 間接符號表里。
3、當我們站在動態(tài)庫的角度去看,全局符號 可以變成導出符號,供外界使用,那我要去脫符號也就意味著,只能去脫不是全局符號的符號。
這里會有一個問題 假如你的一個動態(tài)庫 有好多好多的全局符號,并且 是OC 這里提一點 OC默認的都是導出符號。那么為了縮小體積該怎么辦?
這就需要鏈接器給我們提供的一個參數(shù)不導出符號
-Xlinker -unexported_symbol -Xlinker _OBJC_CLASS_$_LGOneObjc
這樣也就可以把不需要對外暴露的導出符號 變成一個不導出也就是local符號。盡可能的來減小體積。
如果有多個符號不想導出 要如上??一個一個得類似,鏈接器也為我們提供了相應的處理方法
-unexported_symbols_list file
The specified filename contains a list of global symbol names
that will not remain as global symbols in the output file.
The symbols will be treated as if they were marked as __pri-
vate_extern__ (aka visibility=hidden) and will not be global
in the output file. The symbol names listed in filename must
be one per line. Leading and trailing white space are not
part of the symbol name. Lines starting with # are ignored,
as are lines with only white space. Some wildcards (similar
to shell file matching) are supported. The * matches zero or
more characters. The ? matches one character. [abc] matches
one character which must be an 'a', 'b', or 'c'. [a-z]
matches any single lower case letter from 'a' to 'z'.
- 也可以將當前可執(zhí)行文件 用到了哪些庫 所有符號輸出出來
-map map_file_path
Writes a map file to the specified path which details all
symbols and their addresses in the output image.
具體信息可以通過 來查看
man ld
按照符號種類劃分
Symbol type | 說明 ①:?寫代表local symbol |
---|---|
U | undefined(未定義) |
A | absolute(絕對符號) |
T① | text section symbol(__TEXT.__text) |
D① | data Section symbol(__DATA.__data) |
B① | bass section symbol(__DATA.bass) |
C | commom symbol (只能出現(xiàn)在‘MH_OBJECT’類型的‘Mach-O’文件中) |
- | debugger symbol table |
S① | 除了上面所述的,存放在其他‘section’的內容,例如未初始化的全局變量存放在(__DATA,__common)中 |
I | indirect symbol (符號信息相同,代表同一符號) |
u | 動態(tài)共享庫中的小寫u表示一個未定義引用對同一庫中另外一個模塊中私有外部符號 |
通過如下命令真實看一下
nm -pa 地址
Weak Symbol
Weak defintion Symbol :
表示此符號為若定義符號。如果靜態(tài)鏈接器或動態(tài)鏈接器為此符號找到另一個非弱定義,則若定義將被忽略。只能將合并部分中的符號標記為弱定義
- 講道理 如果不對他進行weak修飾,它默認是一個全局符號,還是一個到處符號,我們看一下聲明為若定義之后會不會影響導出符號。
將其加入到Compilp Sources里 并編譯
查看可執(zhí)行文件的導出符號
objdump -macho --exports-trie 地址
- 可以看到并不影響 它是一個全局 導出符號。
- 被hidden修飾的weak 符號 變成 了弱定義的本地符號。
那我在main函數(shù)里寫一個同名實現(xiàn)方法,編譯會報錯嗎?
- 相同的作用域空間,重復的全局符號, 是會報符號沖突的,但是弱定義修飾之后,可以編譯成功。
Weak Reference Symbol:
表示此未定義符號是弱引用。如果動態(tài)鏈接器找不到該符號的定義,則將其符號為0.靜態(tài)鏈接器會將此符號設置弱鏈接標志。
- 通過 weak_import 告訴編譯器 此符號是弱引用的
此時 運行成功,那有什么意義呢?往下看
此時我們可以把 下圖標紅的地方去掉,在運行。
運行
咦報錯了? 為啥報錯,因為在ld連接的過程中 找不到當前符號的地址。
那我們可以通過告訴連接器 這個符號你別管,它是動態(tài)鏈接的,通過如下命令
-U symbol_name
Specified that it is ok for symbol_name to have no defini-
tion. With -two_levelnamespace, the resulting symbol will be
marked dynamic_lookup which means dyld will search all loaded
images.
再次運行
我們想一下 此時他們在同一片作用域空間中,假如 我編寫的是一個 動態(tài)庫,我將所有的符號變?yōu)槿跻梅枺鞘遣皇且馕吨?延遲到dyld加載的時候 找到某個符號的時候 再去做事情 Σ(⊙▽⊙"a
重新導出符號
我們先看一下當前可執(zhí)行文件的符號表
objdump --macho --syms
上面我們將的NSLog 對于我們當前的可執(zhí)行文件來說,
我們看到了 它的符號表它是一個未定義的符號 *UND*,
那假如說別的 庫也想使用我的這個可執(zhí)行文件中的NSLog怎么辦?所以就需要重新導出一下。
同樣連接器也給我們提供了相對的接口
-alias symbol_name alternate_symbol_name
Create an alias named alternate_symbol_name for the symbol
symbol_name. By default the alias symbol has global visibil-
ity. This option was previous the -idef:indir option.
- 這也就意味這我們可以通過其別名的形式讓它進行一個全局可見性。
- 注意只能給間接符號 表里的符號 起一個別名。
我們試一下
我們先看一下當前的符號表
objdump --macho --syms
- 可以看到我們定義符號已經出現(xiàn)了。
還可以用nm 命令查看一下 可更友好的查看
nm -m
再次查看導出符號
objdump -macho --exports-trie 地址
- 可以看到完美的被我們導出了
OC 的符號 默認都是導出符號,因為它是動態(tài)型語言
swift是編譯型語言,所以很多符號在編譯期就知道是什么類型,可通過public private open 等控制。
Common Symbol
在定義時,未初始化的全局符號
未定義符號作用:
1、當它找到定義之后,在編譯連接的時候會把未定義的刪掉。
2、 如果是未定義的符號 鏈接器會把它強制已經定義的 ,比如直接賦值為 0;這就是為什么我定義了一個符號,沒有賦值也沒有使用的時候xcode會報警告 ,這就是鏈接器會識別common 符號 按照一定規(guī)則 ,如你有未定義的 要么給你強制變成 已定義的, 要么給你報警告 要么報錯這是可以選擇的。
strip
首先通過 上面文章 我們知道
對于動態(tài)庫 我們只能去脫不是全局符號的符號。
那對于app來說 我們分析:
1.首先你要考慮要不要給別人使用。那這里這個奇奇怪怪的問題,要不是奇奇怪怪的app 也用不到。
2.通過上面我們也知道 除了間接符號表里的符號不能脫,那是不是意味著別的都可以脫掉 本地的 全局的 弱定義的 都可以干掉 只留下 間接符號表中的。
那對于靜態(tài)庫來說呢?
靜態(tài)庫 是 .o的合集 也就意味著,它里邊還有重定位符號表,那重定位符號表里面是啥來,是保存著你項目用到的符號,那它能刪嗎? 刪了那我連接還怎么重定位。那還有什么可以脫 是不是還有調試符號
大家不妨想一想:
那就符號來說
app 使用靜態(tài)庫體積小 還是動態(tài)庫體積???
答案是靜態(tài)庫 因為 靜態(tài)庫你只能去脫掉調試符號,那app 是不是除了間接符號表 其他的都可以脫掉?
那動態(tài)庫 的符號 是不是最終都放在了app的間接符號表中?