1.gets()函式
問:請找出下面代碼里的問題:
#include<stdio.h> int main(void) { char buff[10]; memset(buff,0,sizeof(buff));gets(buff);printf("\n the buffer entered is [%s]\n",buff);return 0; }答:上面代碼里的問題在於函式gets()的使用,這個函式從stdin接收一個字元串而不檢查它所複製的快取的容積,這可能會導致快取溢出。這裡推薦使用標準函式fgets()代替。
2.strcpy()函式
問:下面是一個簡單的密碼保護功能,你能在不知道密碼的情況下將其破解嗎?
#include<stdio.h>int main(int argc, char *argv[]) { int flag = 0; char passwd[10];memset(passwd,0,sizeof(passwd));strcpy(passwd, argv[1]);if(0 == strcmp("linuxgeek", passwd)) { flag = 1; }if(flag) { printf("\n password cracked \n"); } else { printf("\n incorrect passwd \n");} return 0; }答:破解上述加密的關鍵在於利用攻破strcpy()函式的漏洞。所以用戶在向“passwd”快取輸入隨機密碼的時候並沒有提前檢查 “passwd”的容量是否足夠。所以,如果用戶輸入一個足夠造成快取溢出並且重寫“flag”變數默認值所存在位置的記憶體的長“密碼”,即使這個密碼無法通過驗證,flag驗證位也變成了非零,也就可以獲得被保護的數據了。例如:
$ ./psswd aaaaaaaaaaaaapassword cracked雖然上面的密碼並不正確,但我們仍然可以通過快取溢出繞開密碼安全保護。
要避免這樣的問題,建議使用 strncpy()函式。
作者註:最近的編譯器會在內部檢測棧溢出的可能,所以這樣往棧里存儲變數很難出現棧溢出。在我的gcc里默認就是這樣,所以我不得不使用編譯命令‘-fno-stack-protector’來實現上述方案。
3.main()的返回類型
問:下面的代碼能 編譯通過嗎?如果能,它有什麼潛在的問題嗎?
#include<stdio.h>void main(void) { char *ptr = (char*)malloc(10);if(null == ptr) { printf("\n malloc failed \n"); return; } else { // do some processing free(ptr); }return; }答:因為main()方法的返回類型,這段代碼的錯誤在大多數編譯器里會被當作警告。main()的返回類型應該是“int”而不是“void”。因為“int”返回類型會讓程式返回狀態值。這點非常重要,特別當程式是作為依賴於程式成功運行的腳本的一部分運行時。
4.記憶體泄露
問:下面的代碼會導致記憶體泄漏嗎?
#include<stdio.h>void main(void) { char *ptr = (char*)malloc(10);if(null == ptr) { printf("\n malloc failed \n"); return; } else { // do some processing }return; }答:儘管上面的代碼並沒有釋放分配給“ptr”的記憶體,但並不會在程式退出後導致記憶體泄漏。在程式結束後,所有這個程式分配的記憶體都會自動被處理掉。但如果上面的代碼處於一個“while循環”中,那將會導致嚴重的記憶體泄漏問題!
提示:如果你想知道更多關於記憶體泄漏的知識和記憶體泄漏檢測工具,可以來看看我們在valgrind上的文章。
5.free()函式
問:下面的程式會在用戶輸入'freeze'的時候出問題,而'zebra'則不會,為什麼?
#include<stdio.h>int main(int argc, char *argv[]) { char *ptr = (char*)malloc(10);if(null == ptr) { printf("\n malloc failed \n"); return -1; } else if(argc == 1) { printf("\n usage\n"); } else { memset(ptr, 0, 10);strncpy(ptr, argv[1], 9);while(*ptr != 'z') { if(*ptr == '') break; else ptr++; }if(*ptr == 'z') { printf("\n string contains 'z'\n"); // do some more processing } free(ptr); }return 0; }答:這裡的問題在於,代碼會(通過增加“ptr”)修改while循環里“ptr”存儲的地址。當輸入“zebra”時,while循環會在執行前被終止,因此傳給free()的變數就是傳給malloc()的地址。但在“freeze”時,“ptr”存儲的地址會在while循環里被修改,因此導致傳給free()的地址出錯,也就導致了seg-fault或者崩潰。
6.使用_exit退出
問:在下面的代碼中,atexit()並沒有被調用,為什麼?
#include<stdio.h>void func(void) { printf("\n cleanup function called \n"); return; }int main(void) { int i = 0;atexit(func);for(;i<0xffffff;i++);_exit(0); }這是因為_exit()函式的使用,該函式並沒有調用atexit()等函式清理。如果使用atexit()就應當使用exit()或者“return”與之相配合。
7. 寫一個“標準”宏min,這個宏輸入兩個參數並返回較小的一個。
#define min(a,b) ((a) <= (b) (a) : ))
這個測試是為下面的目的而設的:
1). 標識#define在宏中套用的基本知識。這是很重要的,因為直到嵌入(inline)操作符變為標準c的一部分,宏是方便產生嵌入代碼的唯一方法,對於嵌入式系統來說,為了能達到要求的性能,嵌入代碼經常是必須的方法。 2). 三重條件操作符的知識。這個操作符存在c語言中的原因是它使得編譯器能產生比if-then-else更最佳化的代碼,了解這個用法是很重要的。 3). 懂得在宏中小心地把參數用括弧括起來 4). 我也用這個問題開始討論宏的副作用,例如:當你寫下面的代碼時會發生什麼事? least = min(*p++, b);
8. 嵌入式系統中經常要用到無限循環,你怎么樣用c編寫死循環呢?
這個問題用幾個解決方案。我首選的方案是: while(1) { } 一些程式設計師更喜歡如下方案: for(;;) { } 這個實現方式讓我為難,因為這個語法沒有確切表達到底怎么回事。如果一個應試者給出這個作為方案,我將用這個作為一個機會去探究他們這樣做的 基本原理。如果他們的基本答案是:“我被教著這樣做,但從沒有想到過為什麼。”這會給我留下一個壞印象。 第三個方案是用 goto loop: ... goto loop; 應試者如給出上面的方案,這說明或者他是一個彙編語言程式設計師(這也許是好事)或者他是一個想進入新領域的basic/fortran程式設計師。
9. 嵌入式系統總是要用戶對變數或暫存器進行位操作。給定一個整型變數a,寫兩段代碼,第一個設定a的bit 3,第二個清除a 的bit 3。在以上兩個操作中,要保持其它位不變。
對這個問題有三種基本的反應 1). 不知道如何下手。該被面者從沒做過任何嵌入式系統的工作。 2). 用bit fields。bit fields是被扔到c語言死角的東西,它保證你的代碼在不同編譯器之間是不可移植的,同時也保證了的你的代碼是不可重用的。我最近不幸看到 infineon為其較複雜的通信晶片寫的驅動程式,它用到了bit fields因此完全對我無用,因為我的編譯器用其它的方式來實現bit fields的。從道德講:永遠不要讓一個非嵌入式的傢伙粘實際硬體的邊。 3). 用 #defines 和 bit masks 操作。這是一個有極高可移植性的方法,是應該被用到的方法。最佳的解決方案如下: #define bit3 (0x1<<3) static int a; void set_bit3(void) { a |= bit3; } void clear_bit3(void) { a &= ~bit3; } 一些人喜歡為設定和清除值而定義一個掩碼同時定義一些說明常數,這也是可以接受的。我希望看到幾個要點:說明常數、|=和&=~操作。
10. 嵌入式系統經常具有要求程式設計師去訪問某特定的記憶體位置的特點。在某工程中,要求設定一絕對地址為0x67a9的整型變數的值為0xaa66。編譯器是一個純粹的ansi編譯器。寫代碼去完成這一任務。
這一問題測試你是否知道為了訪問一絕對地址把一個整型數強制轉換(typecast)為一指針是合法的。這一問題的實現方式隨著個人風格不同而不同。典型的類似代碼如下: int *ptr; ptr = (int *)0x67a9; *ptr = 0xaa55;
一個較晦澀的方法是: *(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二種方案,但我建議你在面試時使用第一種方案。