面試達人手冊 - C語言知識點(持續更新)

1. 編譯器

1.1 什麼是GNU?

GNU(“GNU is Not Unix”的遞歸縮寫)是一個自由的操作系統,其軟件內容完全以GPL方式發佈。創始人Stallman宣稱GNU的發音應當爲’Guh-NOO’(注:Gnu在英文中原意爲非洲牛羚,發音與new相同)。

GNU作爲操作系統,但其發展尚未完成,GNU的內核(稱爲Hurd)是主要拖後腿那個東東。在現實中,GNU系統核心多半使用Linux內核、FreeBSD等替代方案。

1.2 什麼是GCC?

Richard Stallman編寫GCC的最初願望是希望有一個C語言的編譯器,那時的GCC含義僅是GNU C Compiler而已。多年發展走來,GCC除了能支持C語言,還支持Objective-C、C++、Java等。GCC不單指GNU C語言編譯器,儼然成爲了GNU編譯器家族(GNU Compiler Collection)了。

編譯器通過文件的擴展名來分辨程序所用的語言。


1.3 GCC的編譯流程?

GCC的編譯流程分爲4步:

預處理
pre-processing
編譯
compiling
彙編
assembling
鏈接
linking

GCC的命令格式
gcc [命令選項] [目標文件] [源文件]

GCC選項 作用
-o 指定GCC的輸出目標文件
-E 編譯器在預處理結束後,停止編譯流程
-S 編譯器在編譯完成後,停止編譯流程
-c 編譯器在完成編譯和彙編後,停止編譯流程
-Wall 打開所有類型語法警告
-On 使用級別n來優化代碼(GCC不同版本的優化效果可能不同)
-O : 主要進行線程跳轉和延遲退棧 進行優化
-O2 : 繼承O1的優化效果; 還有處理器相關的(e.g. 指令調度)優化等
-O3: 繼承O2的優化效果; 還有循環展開等優化;
建議: 在程序調試完成後,再打開優化選項,避免優化問題和bug信息同時處理起來比較困難
-g 添加代碼的gdb調試功能

1.4 GCC的各個編譯步驟的作用?

GCC支持的編譯處理方式表

文件後綴 含義 GCC編譯流程 GCC編譯命令
.c c語言源程序
.m Objective-C語言源程序
.C/.cc/.cxx/.cpp c++語言源程序
.i 預處理過的C程序 預處理編譯、彙編、鏈接 gcc -E -o test.i test.c

預處理:
1) 去註釋
2) 處理“#”開頭的預處理部分(如:條件編譯宏替換、頭文件包含)
3) 預處理變量的處理(如:__file__, __FILE__, __LINE__, __STDC__…)
.ii 預處理過的C++程序 預處理編譯、彙編、鏈接
.s/.S (已經編譯過的)彙編程序 預處理、編譯彙編、鏈接 gcc -S -o test.s test.c/test.i

編譯:
1) 檢查程序規範性和語法錯誤等
2) 翻譯生成彙編文件".s/.S"
.o 已經彙編過的二進制文件 預處理、編譯、彙編鏈接 gcc -c -o test.o test.s

彙編:
1) 生成二進制文件"*.o"
.a/.so 編譯後的庫文件 預處理、編譯、彙編、鏈接 gcc -o test test.o

鏈接:
1) 指定函數庫路徑
2) 多文件整合

2. 如何使用GDB調試器?

GDB是GNU開源組織發佈的一個Linux下強大的程序調試工具。

使用步驟 相關命令
1) 帶參‘-g’編譯源程序test.c,生成a.out gcc -g -o a.out test.c
2) 使用gdb工具運行程序a.out,進入gdb模式 gdb a.out
3) 使用gdb命令
gdb命令 相關釋義
l 查看源代碼
b 30 設置第30行爲斷點
info b 查看斷點的信息
p val 查看變量 ‘val’ 的值
r 運行
c 繼續程序的運行
n 單步運行
q 退出

3. 靜態庫/動態庫的區別、生成和使用?

3.1 庫的命令方式?

  • lib[庫的名稱][.a/.so]

3.2 靜態庫和動態庫的比較?

庫的類別 特點/作用
動態庫(後綴".so") 動態庫不會在編譯後包含到可執行代碼中,而是在程序運行是才被臨時載入,因此程序運行時需要動態庫存在,但是程序代碼體積較小
靜態庫(後綴".a") 靜態庫在編譯後被編譯到生成的可執行文件中,因此可執行文件體積會龐大,但程序運行時就不需要庫。

3.3 製作方法?

1)靜態庫的製作

步驟 釋義
1) 生成目標文件 gcc -c -o test.o test.c
2) 將目標文件編譯生成靜態庫 ar -crs libtest.a test.o

2)動態庫的製作

步驟 釋義
1) 生成目標文件, 該文件是位置無關代碼 gcc -c -fPIC -o test.o test.c
2) 將目標文件編譯生成動態庫 gcc -shared -o libtest.so test.o

3.4 使用方法?

1)靜態庫的使用(e.g. libtest.a)

gcc -static -o a.out main.c  -L /usr/lib  -ltest
  • -L : 用於指定庫文件的路徑
  • -l(小寫字母): 用於指定編譯時鏈接的庫的名稱
  • -static: 選項表示強制使用靜態庫編譯(當有同名同路徑的動態庫和靜態庫存在時,系統默認選擇動態庫)

2)動態庫的使用(e.g. libtest.so)

gcc  -o main main.c  -L . -ltest
  • -L: 用於指定庫文件的路徑
  • -l(小寫字母): 用於指定編譯時鏈接的庫的名稱

3.5 動態庫的加載方法?

三種加載方法 示例
方法1 將庫移動到 /usr/lib 或者 /lib下
方法2 1) 在LD_LIBRARY_PATH環境變量中加上庫所在路徑
export LD_LIBRARY_PATH=~/work/lib/
2) 查看環境變量:echo $LD_LIBRARY_PATH
方法3 1) 在/etc/ld.so.conf.d/目錄中添加一個文件,文件內容爲動態庫路徑
2) 執行 ldconfig 重新啓動配置文件

4. 關鍵字

4.1 什麼是空類型?

空類型是C語言的數據類型中的一種(基本類型、空類型、構造類型);目前空類型分類中只有void。空類型並非無類型,本身也是一種數據結構,常用在類型轉換和參數傳遞的過程中。

1) void

  • 對函數返回值的限定
    • 返回值爲void,表示函數沒有返回值
  • 對一般變量的限定
    • void* 可以指向任何類型的指針變量(如:int a=0; void* p = &a; p則爲“無類型指針,指向變量a的地址”)。
    • 但是不能直接使用void來修飾一個變量:void a;在編譯的時候會報錯。

4.2 const

const修飾的變量特點
1 const修飾的變量爲常變量不是常量
2 const修飾變量時(const int a = 1;等價於int const a = 1;),一旦被初始化後,不能再被直接賦值修改,否則編譯器會報錯。
3 const 修飾指針本身時(int* const p;),( ‘const’的位置:在 ‘*’ 的右側,靠近指針變量本身),指針p本身不能被改變。
4 const 修飾指針指向的對象時(int const *p;),( ‘const’的位置:在 ‘*’ 的左側,遠離指針變量本身),指針p指向的內容不能被改變。

4.3 extern

序號 extern作用
1 修飾外部變量 :
1) 防止重複定義
2) 聲明(引用)外部變量
2 修飾外部函數(注:可省略)
1) 向編譯器聲明函數來源外部
2) 可省略:現代編譯器自動會在所有參與編譯的源文件中查找函數對應的聲明
3 修飾C++中調用的C語言函數
1) 向C++編譯器聲明:參與編譯的C函數按照C++編譯方式命名

4.4 volatile

volatile 聲明變量時,該變量在編譯時會“免優化”。

1)變量用volatile聲明的含義:

變量值可被某些編譯器未知的因素更改,如:操作系統、硬件或者其它線程等。

2)遇到volatile關鍵字聲明的變量:
  • 編譯器對訪問該變量的代碼就不再進行優化;
  • 系統總是重新從它所在的內存讀取數據,即使它前面的指令剛剛從該處讀取過數據。

4.5 register

register 修飾的變量可存放在cpu寄存器中(變量是否存貯在寄存器中取決於實際寄存器的富餘程度,應當儘量減少使用該關鍵字來修飾變量),便於快速訪問那些高頻訪問的數值。

5. 數組、指針、函數(數組指針、指針數組、函數指針…)

6. 作用域

7. 內存空間分佈

一個程序的內存空間分佈 釋義
stack \|/

heap /|\
1. 爲函數內部的局部變量提供存儲空間。
2. 進行函數調用時,存儲“過程活動記錄”。
3. 用作暫時存儲區。(如:計算一個很長的算術表達式時,可以將部分計算結果壓入堆棧。)
bss BSS (Block Started by Symbol)段
1. 存儲未初始化 或 初始化爲0的全局變量、靜態變量
2. 未初始化的數據並不分配空間,只是記錄所需空間大小
3. bss數據段不佔用可執行文件的空間,其內容由操作系統初始化(清零)
data 1. 數據段存儲經過初始化的全局和靜態變量
2. data數據段佔用可執行文件的空間
3. data和.bss在加載時合併到一個Segment(Data Segment)中,這個Segment是可讀可寫的
text 1. 二進制形式的程序代碼、函數;
2. 在其中.rodata段也可包含一些只讀的常數變量,例如字符串常量等。
3.有些常量並不在只讀數據區(如:立即數與指令編譯在一起直接放在代碼段)
4. 程序加載運行時,.rodata段和.text段通常合併到一個Segment(Text Segment)中,操作系統將這個Segment的頁面只讀保護起來,防止意外的改寫
#include <stdlib.h>  

int a=1; 			//a, 在數據段(全局已初始化數據區)
char *p;			//p,在BSS(未初始化全局變量)   
  
int main()  
{
	int b; 							//b 在棧區(局部變量)
	char s[]="abc"; 				//s在棧區(局部數組變量); "abc"在常量區(.rodata)  
	char *p1; 						//p1在棧區(局部變量)
	char *p2="123"; 				//p2在棧區,"123"在常量區(.rodata)  
	static int c;	 				//c在bss段; 靜態局部變量會自動初始化(因爲BSS區自動用0或NULL初始化)  
	p1=(char*)malloc(10); 			//分配得來的10個字節的區域在堆區   
	free(p1);  
	p1=NULL; 						//顯式地將p1置爲NULL,避免以後錯誤地使用p1  
} 

8. 內存安全

9. 代碼規範

發佈了24 篇原創文章 · 獲贊 25 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章