本文主要介紹了一下在Linux下開發c/c++時候,不可避免的會開發或者生成.o .a .so這種中間庫狀態的文件(可能是自己寫了一個lib讓別人調用,或者提供.c/.cpp文件嵌入別人的Makefile工程)。如何查看這些庫文件的一些基本信息。有時候大家編譯程序時候(確切的說是鏈接器鏈接的時候)很多錯誤例如"undefine reference",之類的常見錯誤,原因就是因為沒有找到.o .a .so的庫文件,導致鏈接失敗。
-------------------------------------------------------------------------------
1、Linux庫文件
2、庫文件的使用方式
3、利用tar/nm查看庫文件的信息
-------------------------------------------------------------------------------
1、庫文件的定義之類的就不在此累贅了,有興趣Google之。說白了就是我們寫好一些對應的.h和.c(.cpp)文件,然後通過編譯器的編譯,生成中間代碼供他人使用,他人只需要將你的中間代碼include進自己的程序即可。注意,編譯器編譯成最終可執行的文件需要好幾步,基本可以分為:文本解析->語法解析->此法分析->預處理分析->編譯->連接。生成中間庫是沒有鏈接階段的,在Linux Gcc下通過-C參數指定只編譯不鏈接,所以如果寫了一個.c文件用到了比如pthread_create之類的外部調用,在Gcc -C編譯的時候不用-lpthread因為這個時候是不需要鏈接的。
2、(對庫文件熟悉話直接跳過)在各個系統平台上,庫文件的格式和形式各不相同,Windows下就是如同xxx.dll或者xxx.lib,*inux下就是xxx.so或者xxx.a。兩種分別對應的是靜態庫和動態庫,靜態庫會連同編譯器編譯鏈接進入程序成為程序的一部分,好處是作為程序的一部分不用每次運行時都去load(弊端是可能很多進程都用到這個庫但是每個進程中都有一份,動態庫的話內存中只有一份,通過重定向來加載),而且不會導致因庫的缺失而運行失敗,壞處是會導致可執行文件偏大。動態庫是程序運行時動態加載到進程裡去的,而且可以多進程共,並且方便軟件更新,直接替換老的庫即可。
到底使用靜態還是動態庫取決於程序的使用上下文環境,一般第三方庫都提供了兩種版本,系統庫的話一般都是動態鏈接庫,因為同樣系統下的庫都是一樣的。用Linux的Gcc舉例:
一、寫了一個.h聲明一個foo()函數,然後在.c或者.cpp中實現foo()函數
二、gcc(g++) -c -o foo.o foo.c (注意此處不需要.h頭文件,頭文件只是對庫的對外接口描述)
三、生成靜態庫: ar -r libfoo.a foo.o (靜態庫.a其實就是.o文件的壓縮包,注意這裡不支持把.a打入.a)
生成動態庫: gcc foo.c -fPIC -shared -o libfoo.so(-fPIC意思是生成位置無關代碼,因為動態庫是運行時加載的,需要對代碼進行重定向,不清楚可以Google一下)
四、寫個含有main函數的文件,並調用foo()函數 : gcc(g++) -o test test.c -lfoo,這裡-lfoo意思是去找以lib開頭的某.so或.a文件,默認優先找.so動態庫。 www.2cto.com
需要注意一下的是,如果是cpp引用了c的庫或.c那麼頭文件裡要用externc "C"關鍵字來指定按c的方式讀取(根本上是因為c和c++的函數簽名不一致,因為c++支持重載,所以按c++的方式是找不到同名的c函數的)。在使用動態庫的情況下,程序回去一些預定義的地方找.so文件。比如/usr/lib/下,如果需要自己指定,請修改/etc/ld.so.conf文件。並用ldconfig來刷新cache。
3、如果我們需要查看自己寫的庫的信息時可以用nm來查看,如查看庫中有哪些函數,有哪些全局變量,有哪些依賴別的庫的東西等等,下面我們寫一個例子來說明一下:
[cpp]
#include <stdio.h>
int g1;
int g2 = 0;
static int g3;
static int g4=0;
const int g5=0;
static const int g6 = 0;
int main(int argc, char *argv[])
{
static int st = 0;
int t1;
int t2 = 0;
const int t3 = 0;
printf("printf-function");
return 0;
}
void foo1(){}
static void foo2(){}
[cpp] view plaincopyprint?
void overload(int i){}
[cpp] view plaincopyprint?
void overload(float i){}
linux的nm命令可以一個文件中的符號列表,列出以上代碼Gcc -c編譯出的a.o(a.a a.so)可以通過nm命令來查看其中的符號信息:
[cpp]
0000000000000000 t
0000000000000000 d
0000000000000000 b
0000000000000000 r
0000000000000000 r
0000000000000000 n
0000000000000000 n
0000000000000000 B g1
0000000000000004 B g2
0000000000000008 b g3
000000000000000c b g4
000000000000001c r g5
0000000000000020 r g6
U __gxx_personality_v0
0000000000000000 T main
0000000000000000 a nm.cpp
U printf
000000000000003e T _Z4foo1v
0000000000000044 t _Z4foo2v
0000000000000054 T _Z8overloadf
000000000000004a T _Z8overloadi
0000000000000010 b _ZZ4mainE2st
其中左邊第一列是符號的地址值,對應源碼可以看出遞增的規律。第二列是該符號的類型,第三列是符號的名稱(比如函數名,變量名):
符號類型:介紹幾個最常用的,其他的如果遇到了直接Google:
B --- 全局非初始化數據段(BBS段)的符號,其值表示該符號在bss段中的偏移,如g1
b --- 全局static的符號,如g3
r --- const型只讀的變量(readonly)
N --- debug用的符號
T --- 位於代碼區的符號,比如本文件裡的函數main foo
t --- 位於代碼區的符號,一般是static函數
U --- 位於本文件外的調用函數或變量符號,比如系統的printf()函數
這裡要注意的是,本人使用g++編譯的,所以是按c++的支持重載的函數風格編譯的,可以看到所有函數均帶了前綴和後綴,前綴代表屬於類的名字,後綴代表參數列表的類型縮寫,因為重載必須是區分參數類型,這裡也可以看出,為什麼返回值不同的函數不是重載,因為符號表裡沒有返回值的記錄。
例如兩個overload函數的後綴分別是f和i代表一個是float型一個是int型(上面還有v ->void型)。
nm命令對大家調試多模塊的程序很有用處,大部分情況下可以解決"undefined reference"的問題,如果大家發現nm的另類很好的用法,也可以留言哈!!!