目錄
內存管理
內存分配
堆和棧的區別?(面試重點)
申請內存的函數
malloc
realloc?
free
gcc工具鏈
編譯的過程(面試重點)
第一步,預處理:
第二步,編譯:
第三步,匯編:
第四步,鏈接:
常用參數
靜態庫和動態庫的制作(開發重點)
靜態庫的制作過程
動態庫的制作過程
上一篇復習了聯合體和枚舉,這一篇開始講解一下內存管理和gcc工具鏈。
說明:我們學過單片機的一般都是有C語言基礎的了,網上關于C語言的資料有很多,大家如果對C語言不熟悉的話可以先去詳細學一下,再以這篇博文作為復習資料學習。
這篇博文的目的是復習C語言,我們會陸續以30多個編程題作為復習要點,這30多個編程題基本涵蓋了C語言所有的內容了,只要你掌握了這30多個編程題,那么你的C語言基本就沒什么問題了。
注意:由于本專欄是嵌入式全棧開發專欄,為了我們能熟悉以后實際工作中的開發環境,我們寫C語言全部在Linux中的vim編輯器中寫,這么做事為了我們能夠熟練掌握Linux系統的常用命令以及Linux上的vim編輯器的常用工作命令,以達到對口訓練的目的!
vim編輯器的一些工作命令在上一篇博文中已經詳細介紹過了,如果不了解可以先去看看。
我們正式開始:
內存管理
C語言不能直接操作物理內存,程序中使用的內存都是虛擬內存(一個進程再啟動的時候,系統都會給它分配4個G的虛擬內存,一般情況下是按1:3來分配的,1個G給系統內核來使用,3個G給應用層來使用)。
內存分配
3個G的內存又分為:
注:
以上表格有的時候會分為5段,就是把數據段分為數據段和BSS段,即已初始化數據段
和未初始化數據段。
有的時候將以上表格分為3段,就是將堆和棧分到一起,稱為堆棧。
靜態數據區也在數據段,比如static修飾的變量;
代碼段存放的是編譯好的二進制文件;
例如:
//全局變量
int num; //未初始化數據段,BSS段
char ch=’x’; //已初始化數據段int main()
{char*s=”helloworld”;// s存放在棧空間,”helloworld”屬于只讀數據段,不能被修改s[0]=’x’;//這樣寫是不行的,”helloworld”屬于只讀數據段,不能被修改static int a=0;//靜態數據區char *p=(char*)malloc(sizeof(char)*128);//p棧空間,申請的128個字節屬于堆空間char p1[ ]=”helloworld”; //p1是局部變量在棧空間,”helloworld”放在p1里面的,所以”helloworld”就是在棧空間char *p2=”helloworld”; //p2屬于棧空間,占8個字節,它指向了只讀數據區的”helloworld”return 0;
}
堆和棧的區別?(面試重點)
- 堆空間是用戶管理的,用戶申請,用戶釋放;而棧空間是系統管理的,當用戶定義一個變量的時候,系統會自動為它開辟一個空間,當一段程序運行完時候,系統會自動釋放掉這個空間。
- 堆空間更大,棧空間更小,如果我們要申請連續的大內存,比如說10萬個整數,我們可以去堆空間申請。
- 堆空間使用效率低(因為空間大,內存記在一個鏈表里面,申請內存時,它需要去查一下鏈表,查一下哪邊可以使用),棧空間使用效率高(一直往前申請就可以了)。
- 堆空間函數結束不會釋放(需要用戶手動釋放),棧空間函數結束自動釋放。
申請內存的函數
malloc
函數原型:
void *malloc(size_t size)
realloc?
函數原型:
void *realloc(void *ptr, size_t size)
如果你用malloc申請128個字節不夠用了,可以通過realloc從128個字節后面即第129個字節開始再申請一些空間,但是前提是后面夠用。
如果第128個字節后面不夠用了,它會在其他的地址給你申請一個空間,將前面128個字節的內容拷貝進去,然后再在后面給你申請一塊空間,返回一個新地址給你使用。
所以realloc相當于是拓展內存的。
free
動態申請的內存需要手動釋放,如果不釋放,會造成內存泄漏。釋放后的指針應該置為空指針,否則會變成野指針。
gcc工具鏈
這一節我們來詳細講一節關于編譯的事情。
一般我們在Linux終端想要編譯我們寫好的.c文件的話,直接就是輸入“gcc 文件名.c -o 文件名”就行,但其實這一個過程是分為4個步驟進行的:
編譯的過程(面試重點)
第一步,預處理:
處理所有以#開頭的代碼,包括頭文件、宏定義、條件編譯
比如:gcc -E hello.c -o hello.i
注:.i就是預處理后的文件。
頭文件預處理過程就是將頭文件直接展開。意思就是它會到操作系統里面把stdio.h這文件給找出來,然后將這個文件的內容復制一份粘貼出來,這就是將頭文件展開的操作。
頭文件的”<>”尖括號就是表示到系統指定的目錄下面去找。
這是頭文件的預處理過程。
宏定義比如#define PRICE 10 ???int sum=PRICE*100;預處理的話直接就是將PRICE替換成int sum=10*100
條件編譯的預處理過程就是直接把#if 0....#endif框選的代碼丟掉。
如果你寫了一段代碼后不需要它了,就可以這樣:
#if 0
Void f1()
{
Printf(“helloworld\n”);
}
#endif
這種操作類似于/* ?*/注釋掉代碼。
第二步,編譯:
語法檢查以及將C語言變成匯編語言
比如:gcc -S hello.i -o hello.s
注:編譯后的文件是.s文件,也就是匯編文件。
第三步,匯編:
將匯編語言變成二進制文件
比如:gcc -c hello.s -o hello.o
注:.o文件就是二進制文件。
以ELF開頭,就是二進制文件:
我們可以用file來看一下
第四步,鏈接:
鏈接代碼需要用到的其他文件(庫文件等)
比如:gcc hello.o -o hello
上一步得到的.o文件還是不能夠直接執行,是因為它還差一步。比如我們剛剛寫的這個代碼用到了printf,當程序從main()函數開始執行到printf這里時,它不知道printf是什么東西,我們還差一步鏈接的操作,即把“printf這個代碼在哪”這個信息鏈接到原文件里面去。
鏈接有兩種,一個是靜態鏈接,一個是動態鏈接(我們直接寫的“gcc 文件名.c -o 文件名”就是一種動態的鏈接)。
動態鏈接就是將“printf這個代碼在哪”這個信息鏈接到原文件里面去,等到運行的時候它會根據我們提示的位置信息就找到printf()這個函數,這樣才能執行。如果libc文件丟了則不能執行了。
比如gcc hello.o -o hello-動態
靜態鏈接生成的二進制文件比動態鏈接的文件大。因為靜態鏈接時是直接將libc中的printf()函數的實現放到生成的二進制文件中。但是,這種方式在libc文件丟失的情況下還是可以執行。
比如gcc hello.o -o hello-靜態 -static
常用參數
-c:只是編譯不鏈接,生成目標文件“.o”
-S:只是編譯不匯編,生成匯編代碼
-E:只進行預編譯,不做其他處理
-g:在可執行程序中包含標準調試信息,用gdb調試程序的時候用到。
-o file:把輸出文件輸出到file里
-V:打印出編譯器內部編譯各過程的命令行信息和編譯器的版本
-I dir:大寫的i,在頭文件的搜索路徑列表中添加dir目錄,如果我們寫的頭文件和.c文件不是在同一個文件中,編譯的時候可以將頭文件放在-I后面。
-L dir:在庫文件的搜索路徑列表中添加dir目錄,鏈接靜態庫和動態庫的時候用到。
-static: 鏈接靜態庫
-l library :連接名為library的庫文件
靜態庫和動態庫的制作(開發重點)
靜態編譯用到靜態庫,動態編譯用到動態庫;
靜態庫的制作過程
什么是靜態庫?怎樣制作靜態庫?
1、寫一個hello.c文件,里面調用了f1()和f2()兩個函數,然后f1()和f2()的實現分別寫在單獨的文件f1.c和f2.c中。
hello.c
f1.c
f2.c
?
2、接下來首先第一步是將f1.c和f2.c兩個原文件轉換成二進制文件。輸入”gcc -c f1.c f2.c”這樣就會自動生成兩個.o文件。
?
3、然后我們用“ar -crv lib(靜態庫的名字隨便取).a f1.o f2.o”,注意一定是以lib開頭,加上靜態庫的名字,.a是靜態庫的后綴,比如“ar -crv libx.a f1.o f2.o”x就是靜態庫的名字。最終生成的libx.a文件就是靜態庫。
?
4、當我們拿到這個靜態庫的時候,要執行hello.c文件就要結合這個靜態庫才行,比如“gcc hello.c -o hello -static -L . -l x”。
注:“.”表示在當前目錄下面。-static是靜態鏈接,-L 后面接庫文件的路徑,-l后面是庫文件的名字。
這樣它就生成了hello的二進制文件
?
5、接下來我們改一下hello.c文件,在main()函數的上方聲明一下f1和f2這兩個函數。
聲明之后我們就可以正常編譯hello.c文件了。
動態庫的制作過程
什么是動態庫?怎樣制作動態庫?
1、同上,寫好f1.c和f2.c和hello.c文件之后,輸入“gcc -fPIC -shared -o lib庫的名字.so f1.c f2.c”,比如gcc -fPIC -shared -o libxx.so f1.c f2.c,最終生成的libxx.so就是動態庫。
?
2、我們編譯hello.c文件之前先鏈接動態庫,“gcc hello.c -o hello -L . -l xx”。
?
3、鏈接好之后用”ldd?加上”gcc hello.c -o hello -L . -l xx”這一步操作后生成的二進制文件名”來看看它會默認去取哪里找,比如我們看到它會去/lib這個目錄下面去查找,我們將libxx.so動態庫拷貝到/lib目錄下。
注:不同的操作系統可能會去不同的目錄下去找,”ldd?加上”gcc hello.c -o hello -L . -l xx”這一步操作后生成的二進制文件名”可以查找二進制文件所用到的庫在哪。
拷貝進去/lib中
這個時候我們再執行hello就成功了
以上就是這篇內容,如想了解更多,歡迎訂閱本專欄!
如有問題可評論區或者私信留言,如果想要進交流群請私信!