天天看點

C語言面試複習

1、C代碼變成可執行檔案的過程:

  • 預處理:

    把源檔案翻譯成預處理檔案

    gcc -E code.c 顯示預處理的結果

​ gcc -E code.c -o code.i 生成以.i結尾的預處理檔案

  • 編譯:

    ​ 把預處理檔案翻譯成彙編檔案

    ​ gcc -S code.i 生成以.s結尾的彙編檔案

  • 彙編:

    ​ 把彙編檔案翻譯成二進制的目标檔案

    ​ gcc -c code.s 生成以.o結尾的目标檔案

  • 連結:

    ​ 把若幹個目标檔案合并成一個可執行檔案

    ​ gcc code.o a.o b.o c.o … 預設生成一個a.out的可執行檔案

2、break和continue的差別

1.當它們用在循環語句的循環體時

​ break用于立即退出本層循環

​ continue僅僅結束本次循環(本次循環體内不執行continue語句後的其它語句,但下一次循環還會繼續執行)

2.如果有多層循環時,break隻會跳出本層循環,不會跳出其他層的循環

  1. break可用于switch語句,表示跳出整個switch語句塊,而continue則不能單獨的用于switch語句。但是continue可以用于循環内部的switch語句。
  2. break和continue語句在循環内的switch語句中使用時,是有差別的。在這種情況下的break是指跳出switch語句塊(switch語句塊之後的代碼仍然執行)。而這種情況下的continue是指結束本次循環(不在執行switch後面的代碼),進行下一次循環

3、switch()中不允許的資料類型有?

(1)浮點型:浮點型無法精确比較,由于精度問題。

(2)字元串:字元串沒有直接的比較操作符可以使用,隻能通過strcmp之類的函數進行比較,也不适合。

4、static在C語言中的作用

靜态全局變量僅對目前檔案可見,其他檔案不可通路,其他檔案可以定義與其同名的變量,兩者互不影響。

在定義不需要與其他檔案共享的全局變量時,加上static關鍵字能夠有效地降低程式子產品之間的耦合,避免不同檔案同名變量的沖突,且不會誤使用。

非靜态全局變量的作用域是整個源程式, 當一個源程式由多個源檔案組成時,非靜态的全局變量在各個源檔案中都是有效的。 而靜态全局變量則限制了其作用域, 即隻在定義該變量的源檔案内有效, 在同一源程式的其它源檔案中不能使用它。由于靜态全局變量的作用域局限于一個源檔案内,隻能為該源檔案内的函數公用,是以可以避免在其它源檔案中引起錯誤。

把局部變量改變為靜态變量後是改變了它的存儲方式即改變了它的生存期。把全局變量改變為靜态變量後是改變了它的作用域,限制了它的使用範圍。

5、程序映像

text 代碼段(隻讀段): 存儲二進制指令、常量,權限是隻讀,強制修改會産生段錯誤

data 資料段: 存儲初始化過的全局變量(如果初始化為0,會被存儲在bss),初始化過的靜态局部變量

bss 靜态資料段: 未初始化過的全局變量、未初始化過的靜态局部變量,程式運作時會被清理為0

heap 堆: 由程式員手動管理的,足夠大

stack 棧: 存儲局部變量、塊變量,會随着程式運作不斷申請、釋放 (由作業系統管理,小)

environ 環境變量表: 環境變量

argv 指令行參數: 程式執行時附加的參數

6、什麼是指針:

指針是一種特殊的資料類型,使用它可以定義指針變量,指針變量中存儲的是整型資料,代表了記憶體的編号,通過這個編号可以通路到對應的記憶體。

為什麼使用指針:

1、由于函數與函數之間是互相獨立的,但是有些時候需要共享變量

​ 傳參是單向值傳遞

​ 全局變量盡量少用,容易命名沖突

​ 使用數組還需要傳遞長度

​ 函數的命名空間是互相獨立的,但是位址空間是同一個,是以指針可以解決這個問題

2、由于函數傳參是值傳遞(記憶體拷貝),對于位元組數較多的變量,值傳遞的效率比較低,如果傳遞的是變量的位址,隻需要傳遞4|8個位元組。

3、堆記憶體無法取名,它不像stack、bss、data讓變量名和記憶體建立關系,隻能使用指針來記錄堆記憶體的位址編号進而使用該堆記憶體。

空指針:

​ 值是NULL的指針變量都是空指針,如果對空指針進行解引用産生段錯誤

​ NULL也是一種錯誤标志,如果一個函數傳回值是指針類型時,當函數執行出錯可以傳回NULL表示該函數執行錯誤。

​ 注意:NULL在絕大多數的系統中都是0,在個别系統是1

​ if(NULL==p) 等價于 if(!p)

​ 如何避免空指針帶來的段錯誤:

​ 使用來曆不明的指針之前先做判斷是否是空指針

​ 1、當函數的參數是指針時,别人傳給你的可能就是空指針

​ 2、當函數擷取傳回值時,也有可能擷取到空指針

野指針:

​ 指針的指向不确定的記憶體的指針。

​ 野指針的危害:

​ 1、段錯誤

​ 2、髒資料

​ 3、一切正常

​ 野指針危害比空指針更嚴重,因為它無法判斷出來,而且可能是隐藏性的錯誤,短時間内不暴露

​ 如何避免:

​ 1、定義指針時一定要初始化

​ 2、函數不傳回局部變量位址

​ 3、指針指向的記憶體被釋放後,指針變量立即置空 = NULL

7、指針的運算:

​ 絕大多數無意義。

​ 指針+n: 指針+指針類型寬度*n

​ 指針 -n: 指針 -指針類型寬度*n

​ 指針-指針:(指針-指針)/ 指針類型寬度 計算出兩個指針直接相隔多少個指針元素

8、堆記憶體和棧記憶體的差別:

堆:是程序中的一個記憶體段,由程式員手動管理。

足夠大

為什麼要使用堆記憶體:

​ 1、随着程式的複雜,資料會越來越多

​ 2、其他的記憶體段的申請和釋放不受控制,堆記憶體的申請釋放受程式員控制

全局的,變量可以調整大小

缺點:使用麻煩,容易産生記憶體碎片

棧:局部的,快速通路,不需要自己管理,自動建立釋放,有大小限制,不會産生記憶體碎片

9、什麼時候用堆,什麼時候棧:

如果我們需要配置設定一大塊記憶體(例如一個很大的數組或者一個很大的結構體),而且我們需要保持這個變量很長時間(例如全局變量)。我們應該配置設定堆記憶體。如果你處理的很小的變量,而且隻要再函數使用的時候存活,那麼你應該使用棧,它比較友善而且快捷。如果你需要類似與數組或者結構體的變量,而且能夠動态改變大小(例如一個數組可以根據需要添加資料或者删除資料),那麼你可以用malloc(),realloc())給他們配置設定堆記憶體,用free()手動的管理記憶體。

10、堆記憶體的越界後果:

1.超過33頁産生段錯誤

2.破壞了malloc的維護資訊,再次使用malloc/free會出錯

3.髒資料

11、字元串函數

#include <stdio.h>
#include <assert.h>

size_t str_len(const char* str)
{
	assert(NULL != str);
	const char* tmp = str;
	while(*tmp) tmp++;
	return tmp - str;
}

char* str_cpy(char* dest, const char* src)
{
	assert(NULL != dest && NULL != src);
	char* tmp = dest;
	while(*tmp++ = *src++);
	return dest;
}

char* str_cat(char *dest, const char *src)
{
	assert(NULL != dest && NULL != src);
	char* tmp = dest;
	while(*tmp) tmp++;
	while(*tmp++ = *src++);
	return dest;
}

int str_cmp(const char *s1, const char *s2)
{
	assert(NULL != s1 && NULL != s2);
	while(*s1 && *s1 == *s2) s1++,s2++;
	return *s1 - *s2;
}
           

12、宏的優點

提高代碼的拓展性(友善批量修改)、提高可讀性、提高安全性

13、什麼是宏函數

其實就是帶參數的宏,不是真正的函數,不檢查參數類型,沒有傳參,沒有傳回值,隻有計算的結果,隻是替換

14、如何避免宏函數二義性

1、宏函數整體加小括号,每個參數都加小括号

2、不要提供帶自變運算符的變量作為參數

15、普通函數與宏函數有什麼差別?

普通函數:是一段具有某項功能的代碼段,會變編譯成二進制指令存儲到代碼段記憶體中,函數名就是首位址,有獨立的命名空間、棧記憶體

宏函數:是一個帶參數的宏,并不是真正的函數,而隻是代碼的替換,僅僅隻是使用起來像函數。

函數: 傳回值 類型檢查 安全 壓棧、出棧 速度慢 跳轉
宏函數: 運算的結果 通用 危險 替換 速度快 備援

16、#define typedef差別

​ #define INT int

​ typedef int INT

​ 是什麼?

​ 差別?

#define 簡單的文本替換,不在編譯中進行

typedef 起别名

typedef int* pINT;
#define pint int*

pINT a,b;
//	a和b都是int*
pint a,b;
//	a是int*  b是int
因為#define隻是單純替換
           

17、結構體的對齊補齊

記憶體對齊:

​ 假定從零位址起,每個成員的起始位址編号必須是它本身位元組數的整數倍

記憶體補齊:

​ 結構體的總位元組數必須是它最大成員位元組數的整數倍,如果不是,則在末尾填充空位元組

​ 在Linux系統下計算結構體的對齊、補齊時,如果成員位元組數超過4 ,則按照4位元組計算

18、結構與聯合的差別

  1. 結構和聯合都是由多個不同的資料類型成員組成, 但在任何同一時刻, 聯合中隻存放了一個被選中的成員(所有成員共用一塊位址空間), 而結構的所有成員都存在(不同成員的存放位址不同)。
  2. 對于聯合的不同成員指派, 将會對其它成員重寫, 原來成員的值就不存在了, 而對于結構的不同成員指派是互不影響的。

19、位元組序列

個人計算機系統一般都是小端系統,UNIX伺服器和網絡裝置都是大端,網絡位元組序也是大端模式資料

小端:高對高 大端:高對低

判斷大小端

union Data
{
	char ch;
	int num;
};

int main(int argc,const char* argv[])
{
	union Data d = {};
	d.num = 0x01020304;
	if(0x4 == d.ch)
	printf("小端\n");
	else
	printf("大端\n");
}
           

20、什麼是枚舉類型

枚舉可以看作是一種類型受限的int類型,把所有可能出現的值列出來,但是C語言中編譯器為了效率不會檢查資料的值

main函數執行之前,還會執行什麼代碼

全局對象的構造函數會在main函數之前執行

定義一個指向函數指針的指針數組,函數有一個整型入參和一個整型傳回值

int (*arr)[10] (int num)