程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 楊力祥老師在C++課後給同學留了一道思考題,即探討C++函數調用時其內存的結構究竟是什麼樣的。在參考《程序員的自我修養》

楊力祥老師在C++課後給同學留了一道思考題,即探討C++函數調用時其內存的結構究竟是什麼樣的。在參考《程序員的自我修養》

編輯:C++入門知識

本文主要介紹了一下在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的另類很好的用法,也可以留言哈!!!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved