看板 C_and_CPP 關於我們 聯絡資訊
※ 引述《lovejomi (JOMI)》之銘言: : 詢問一個撰寫c++ shared library (.so) 給 client使用的問題 : 看到一份code, .so 的header 提供的api prototype是長這樣 : std::unique_ptr<Foo> CreateFoo(); : 這很明顯的是 : allocate的動作在lib裡面 : deallocate的人一定在caller那端也就是client code : a. 這是不是一個很不正確的design? : 基於一個觀念 : 不該把new跟delete的動作, 在不同library間執行 : 之前觀念是因為heap是獨立的, 所以會出問題 下面再講 new/delete 和 heap 獨立的問題.. 通常不會在 API 設計上綁死 object 是怎麼 allocate, 這樣會限縮以後 改變 memory management 的彈性.例如有些 runtime engine 會用自已的 memory pool, 不管中間有什麼 leak, 反正在 release runtime engine 後都就整個 pool 合收 但這樣不代表就不能用 unique_ptr. unique_ptr 只是提供 "自動" 呼叫 deletor 的方式, 但 deletor 還是會由你的 library 提供 foo *fp = createFoo(); releaseFoo(fp); vs. using foo_ptr = unique_ptr<foo, void(*)(foo*)>; foo_ptr createFoo() { return foo_ptr(createFooInternal(), releaseFoo); } foo_ptr fp = createFoo(); 然後沒人用 fp 時幫你呼叫 releaseFoo(fp); 那不是大同小異嗎? : b. 但這觀念是不是只有windows上才有呢? 不確定 : 以下連結也只提到dll : https://stackoverflow.com/questions/443147/c-mix-new-delete-between-libs 跟 e. 一起回答 會有這個問題是因為 Windows DLL 和 Linux Shared Object 的 binding 時機不同 要 call 什麼 function, 在編 DLL 就決定了, 但對 shared object, 是在 runtime 才決定 (可以改這個行為, 但我們以下都以預設的行為討論) 想一下有段 code executable: main.c libfoo.so: foo.c ______________________________________ _________________________________ 1 #include <stdio.h> │ 1 #include <stdio.h> 2 │ 2 3 int g = 1000; │ 3 int g = 2000; 4 │ 4 5 void foo() { │ 5 void foo () { 6 printf ("main: foo %d\n", g); │ 6 printf ("foo: foo %d\n", g); 7 } │ 7 } 8 │ 8 9 void g_plus_plus(); │ 9 void g_plus_plus() { 10 void bar(); │ 10 g++; 11 │ 11 } 12 int main () { │ 12 13 foo(); │ 13 void bar() { 14 g_plus_plus(); │ 14 foo(); 15 bar(); │ 15 } 16 } │ a.out 和 libfoo.so 都有提供 g 變數, 和 foo function, 那這段 code 會印出什麼結果? 在 Linux, 會印出 main: foo 1000 main: foo 1001 但在 Windows 會印出 main: foo 1000 foo: foo 2001 因編譯 DLL 時就決定 DLL 要使用 foo.c 裡的 definition 但 SO 在 runtime 決定要哪一個本版 Linux (或 FreeBSD 等) 基本上是走 System V Application Binary Interface http://www.sco.com/developers/gabi/latest/ch5.dynamic.html 看 Shared Object Dependencies 那一節 細節有點複雜, 不過可以想成, runtime 時才決定 symbol 是哪本版本, 整個程式, 主程式本身和連帶的 SO 都會使用同一個版本, 若 executable 本身和 SO 都有提供, 以 executable 為主. 這樣的規範其實會讓 static link archive library 和 dynamic link so 時 的結果一樣 (當然 function / data 要切分到不同的 object file 避免 redefinition 的問題) 打太久差點忘了為什麼要寫這段 orz 所以在 Windows, 有分 single/multi-thread * debug/release 四種 vc runtime .exe 和 .dll 用不同的 config 編會連到不同的版本, 兩邊會搭不起來 但 Linux 不會有這個問題 shared object 可以用 static, visilibity attribute, 或 linker 的 -Bsymbolic 等來達到類似 DLL 的行為, 但 DLL 應該是沒辦法做到 SO 這樣的行為 (至少我不知道做法) : 針對這件事 我並沒有明確的google到有什麼guideline提到該怎麼設計 : 如果是以我經驗來說 : 多半是 : Foo* Create() 搭配 void Release(Foo*) : 但這感覺比較偏向C, 而這樣好像也限制client端得到這個Foo*後無法用smart pointer去 : hold. : 所以以我的想法會覺得 : 回傳 : weak_ptr<Foo> or shared_ptr<Foo> Create(); 給client : 然後Create函數裡面實作的時候使用一個global之類的container, : 把這sp記錄起來確保 .so是最後一個去delete他的人 : 但這邊衍伸一個疑問 : c. a.exe + b.so 使用load time dynamic link的話, : a.exe還是b.so的global變數會先開始解構? : 如果是使用 run time dynamic link的話 基本上 destructor 是 constructor 倒過來的順序 Linux 會用 .init/.init_array/.fini/.fini_array 做 global 的 constructor destructor load constructor, unload 時 destructor, a.exe 先, b.so 後, 同理 dlclose b.so 時做 destructor, exit a.exe 才 destrucor : d. 如果client用dlopen 然後dlsym拿到一個sp後 : 手動呼叫dlclose....這時候繼續使用這個sp 是不是會有undefined的行為產生? : 就算沒有人解構可是.so已經被close了? 好像沒直接關係? 如果 so 裡 new int(123) 傳回去, 應該是不會怎樣啦, 你是想問如果 object 的 code 在 so裡吧? 我想是會炸掉, 不過為什麼好好的要 dlopen 東西 allocate 東西又再 dlclose 掉? : e. 不能在不同的library間 new delete這件事 是否是platform/compiler dependent? : 假設linux上如果我測試發現沒有出問題 是否表示在這compiler/platform下 上面回了, 基本上是 platform ABI 決定的行為 compiler (整個 toolchain 和lib) 要配合 platform ABI : 100%不會發生問題 你這條件太強了 XD : 還是說"有可能" 不定時的出現問題 而不能馬上當下發現立刻修正? : 因為目前使用那個shared library(return unique ptr)並沒發生問題 : 會不會之後在某特定情況下突然出現問題呢? : 會有 lib 間 heap是獨立的這件事 是gcc / vc實作決定的嗎 還是更底層 OS實作? 我覺得獨立 heap 是稻草人 orz : f. 以我的觀念來看 如果是.a 的static lib, 是不是就完全沒有design上特別的考量 : 不需要特別去因為寫static library而有特別需要注意的地方? : g. 如果一個exe使用多個.so 而這些.so都用不同版本的gcc build出來的 : 這樣如果他們expose的api 含有 stl的type : 是不是就是一個非常不好的design? 不同 gcc 可能會搭不且的 stl library 和 glibc, 混用會有問題 雖然 shared object 有 versioning 的基制, 但實際上還是會有遇一些問題 gcc 的 stl 應該也沒有不同版本的實作完全相同, SO 和執行檔如果會 pass object 給對方, 例如上面 unique_ptr 的 case, 有可能兩邊的 declaration 有差異而 runtime 才發現問題 : 如果我真的要使用這些不同版本的.so 是不是只能祈禱不會出事情而無法作解決? : 而這問題是不是只要這些.so用dynamic link libstdc++就沒事了? : h. 有沒有什麼網頁有特別針對shared library的interface design 有提供guideline? : 想要稍微go through一下比較能掌握一些必要觀念 最重要的這部份我好像沒辦法給建議 @@ 最去經驗是 dynamic link 其實沒有想像中的 protable, 蠻常遇到問題, 但如果你要 static link 又有到 dlopen 等 function, 那 link 時的 glibc 和執行的 glibc 要同一個版本, 不然會有問題. 這樣 static link 其實沒意義 在 Linux 要保證沒問題最好還是要在執行環境重編 : 以上幾個問題有點複雜 請教各位 : 非常感謝 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 114.42.124.212 ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1535034391.A.EEA.html
ilikekotomi: 感謝分享 一直不知道dll和so有這種差別 08/24 00:24
james732: 推 08/24 01:10
cole945: 想了一下我a.講的有點誤導. create出來叫人家自已delete 08/24 09:05
cole945: 的framework也少. 用的人通常自已決定要不要用uniq_ptr去 08/24 09:06
cole945: 接. 但直接return uniq_ptr強迫人家用的自已經驗沒遇過 08/24 09:07
cole945: > 第二推少了一個字 "不少" 08/24 09:07
Bencrie: g 這樣不會重複定義喔?@@a 08/24 09:40
Bencrie: 試了一下 link so 還真的不會撞到 XD 08/24 09:47
AstralBrain: 是個undefined behavior, no diagnostic required 08/24 10:04
lovejomi: 看來要花時間吸收一下,謝謝 08/25 00:27
lovejomi: 不過uniqueptr真的不能使用default deleter嗎 08/25 00:28
lovejomi: 我遇到 a.exe 使用vector<.so type> 然後.so 裡面也使 08/25 00:32
lovejomi: 用這種vector<type>..兩者用不同版本編,link的時候出現 08/25 00:32
lovejomi: warning possible ODR violation....是不是表示a.exe最 08/25 00:32
lovejomi: 終可能是link到.so的vector實作,也可能是自己的vector 08/25 00:33
lovejomi: 實作?決定權在linker? 08/25 00:33
Killercat: namespace一樣的話 對 08/28 22:34
Killercat: 事實上嚴格講起來是symbol symbol就是namespace+arg 08/28 22:37
Killercat: 所以「symbol」一樣的話 linker會直接吐error給你 08/28 22:37
Killercat: 程式設計師的自我修養有相當詳細的解釋 08/28 22:38
Killercat: 有點語意不清 其實symbol的話決定權並非linker, linker 08/28 22:38
Killercat: 唯一能做的不是選擇而是直接靠背出來 08/28 22:38
cole945: 你說的.o link的情況,上面在說的是dynamic linking的情況 08/28 22:51
cole945: 換個方法講,這篇在講的是loader處理module間symbol的問題 08/28 23:06
cole945: 而你在說的是linker link單一module時的問題 08/28 23:06
cole945: 如果說linker不用管選擇symbol也太單純化了,至少處理weak 08/28 23:07
cole945: symbol就有影響了. 雖然我沒認真讀過程式設計師的自我修 08/28 23:10
cole945: 不過我的工作是弄整個toolchain,不至於這個搞不清XD 08/28 23:12