目錄 - 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步:
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;
在編譯的時候會報錯。
- void* 可以指向任何類型的指針變量(如:
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
}