12個有趣的C語言問答(詳解)
1 gets()方法
Q:下面的代碼有一個被隱藏的問題,你能找到它嗎?
1 #include <stdio.h>
2
3 int main(void)
4 {
5 char buff[10];
6 memset(buff, 0, sizeof(buff));
7 gets(buff);
8 printf("%s\n", buff);
9
10 return 0;
11 }
A:這個不顯眼的問題就是使用了gets()方法,其函數原型如下:
char* gets(char *s);
此方法接受一個字符數組參數,但是卻沒有檢查此數組是否有足夠的空間來拷貝數據。gets()函數是不安全的,不推薦使用,一般情況下編譯器也會給出警告提示:the `gets' function is dangerous and should not be used。gets()不檢查預留存儲區是否能夠容納實際輸入的數據。多出來的字符簡單的溢出到相鄰的存儲區,可能會導致錯誤。
所以,這裡我們一般用fgets()方法更好,函數原型如下:
char* fgets(char *s, int n, FILE *stream);
一般使用fgets()函數,都是讀取文件當中的n-1個字符到s中,其實,此函數還有一個很好的用處就是從標准輸入流中讀取字符串,而且不用擔心輸入的字符個數超出了字符數組的大小而導致溢出的問題!要怎樣做呢?如下:
char str[10];
fgets(str, siezof(str), stdin);
值得注意的是:謹記fgets()只讀取n-1個字符。所以,fgets()讀取到換行符、文件尾或讀完n-1個字符便會進行返回。
2 strcpy()方法
Q:密碼防護是很基本的功能,看看能夠搞定下面這一段代碼?
1 #include <stdio.h>
2 #include <memory.h>
3 int main(int argc, char *argv[])
4 {
5 int flag = 0;
6 char passwd[10];
7
8 memset(passwd, 0, sizeof(passwd));
9 strcpy(passwd, argv[1]);
10
11 if (0 == strcmp("LinuxGeek", passwd)){
12 flag = 1;
13 }
14 if (flag){
15 printf("\n Password cracked \n");
16 }else{
17 printf("\n Incorrect password \n");
18 }
19
20 return 0;
21 }
說明:該程序通過在運行時攜帶一個密碼參數,然後程序會將用戶輸入的密碼參數值與真實的密碼比較,如果兩者相等就輸出cracked信息,否則輸出incorrect提示。
3 main()函數的返回類型
Q:請問下面這段代碼能否通過編譯?如果能的話,那麼這段代碼中隱含什麼問題嗎?
#include <stdio.h>
#include <stdlib.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;
}
A:代碼能通過編譯,但是會留下針對main()函數返回值類型的警告。main()函數的真正返回值類型應該是int而不是void,這是因為int返回類型可以返回程序運行的狀態值,尤其是當這段程序作為其他應用的附屬程序時這個狀態值將更加重要。
mainret.c:3:6: warning: return type of ‘main’ is not ‘int’ [-Wmain]
void main(void)
^
4 內存洩漏
Q:請問,以下代碼有內存洩漏嗎?
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *ptr = (char*)malloc(10);
if (NULL == ptr){
printf("\n Malloc failed \n");
return -1;
}else{
//Do some processing
}
return 0;
}
A:不會,雖然上面的代碼沒有對指針ptr進行內存釋放,但實際上即使是程序結束也不會造成內存洩漏,因為當程序結束時所有一開始被占據的內存就全部清空了。但是,如果上面分配內存這段代碼是在while循環裡面那將會造成嚴重的問題。
5 free()方法
Q:以下代碼,當用戶輸入'freeze'時會崩潰,而如果輸入'zebra'則運行正常,為什麼?
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <memory.h>
4 int main(int argc, char *argv[])
5 {
6 char *ptr = (char *)malloc(10);
7
8 if (NULL == ptr){
9 return -1;
10 }
11 if (argc == 1){
12 printf("\n Usage: add a string \n");
13 }else {
14 memset(ptr, 0, 10);
15 strncpy(ptr, argv[1], 9);
16 while (*ptr != 'z'){
17 if (*ptr == ' ') break;
18 else ptr++;
19 }
20 if (*ptr == 'z'){
21 printf("\n String contains 'z' \n");
22 //Do some more processing
23 }
24 free(ptr);
25 }
26
27 return 0;
28 }
A:問題的根源是因為代碼在while循環中改變了 ptr 指針的地址。當輸入為'zebra'時,while循環甚至在執行第一遍前就結束了,所以free()釋放的內存地址就是一開始malloc()分配的地址。但是當輸入'freeze'時, ptr記錄的地址在while循環中被更改,因此將會使錯誤的地址傳遞到free()方法中引起崩潰。
注意:調用free()方法釋放內存時,參數必須要麼是NULL,要麼是先前從malloc/calloc或者realloc返回的地址,不能將一次動態申請的內存的部分釋放。
6 atexit()和_exit()
Q:以下代碼中的atexit()方法並沒有被調用,直到為什麼嗎?
#include <stdio.h>
#include <unistd.h>
void func(void)
{
printf("\n Clean up function called \n");
}
int main(void)
{
int i = 0;
atexit(func);
for (; i < 0xFFFF; i++);
_exit(0);
}
A:這是因為使用了 _exit() 方法。此方法並沒有調用清除數據相關的方法,比如 atexit()等。exit和_exit都是用來正常終止一個進程的,主要區別是_exit會立刻進入內核,而exit先執行一些清除工作(包括執行各種終止處理程序,關閉所有標准I/O等,一旦關閉了IO,例如printf等函數就不會輸出任何東西了),然後才進入內核。這兩個函數會對父子進程有一定的影響,當用vfork創建子進程時,子進程會先在父進程的地址空間運行(這跟fork不一樣),如果子進程調用了exit就會把父進程的IO給關掉。
7 void*與C結構體
Q:能夠設計一個方法接受任意類型的參數然後返回整數?同時,是否有辦法傳遞多個這樣的參數?
A:一個能接受任意類型參數的方法像下面這個樣子:
int func(void *ptr)
如果需要傳遞多個參數,那麼我們可以傳遞包含這些參數的結構體。
8 *與++運算符
Q:以下代碼將輸出什麼?為什麼?
#include <stdio.h>
int main(void)
{
char *ptr = "Linux";
printf("\n [%c] \n", *ptr++);
printf("\n [%c] \n", *ptr);
return 0;
}
A:程序的輸出結果如下:
[L]
[i]
因為++與 * 的優先級一樣,所以 *ptr++ 將會從右向左操作。按照這個邏輯,ptr++ 會先執行然後執行*ptr。所以第一個結果是'L'。也因為 ++ 被執行了,所以下一個printf() 結果是'i'。
9 Making changes in code segment
Q:以下代碼運行時一定會崩潰,你能說出原因嗎?
1 #include <stdio.h>
2 int main(void)
3 {
4 char *ptr = "Linux";
5 *ptr = 'T';
6 printf("\n [%s] \n", ptr);
7
8 return 0;
9 }
A:這是因為字符串常量“Linux”是以只讀的形式存儲的,而通過*ptr='T'語句,此代碼嘗試更改只讀內存存儲的字符串內容,此操作當然行不通,所以才會導致崩潰。
10 Process that changes its own name
Q:你能否寫一個程序,在它運行時修改它的名稱?
A:以下的代碼可以:
1 #include <stdio.h>
2 #include <memory.h>
3
4 int main(int argc, char *argv[])
5 {
6 int i = 0;
7 char buff[100];
8
9 memset(buff, 0, sizeof(buff));
10 strncpy(buff, argv[0], sizeof(buff));
11
12 memset(argv[0], 0, strlen(buff));
13 strncpy(argv[0], "NewName", 7);
14 //Simulate a wait. Check the process name at this point
15 for (; i < 0xFFFFFFFF; i++);
16
17 return 0;
18 }
可以通過下面的方法測試
$ gcc chname.c -o chname
$ ./chname &
[1] 4677
$ ps 4677
PID TTY STAT TIME COMMAND
4677 pts/11 R 0:08 NewName
11 局部變量的返回地址
Q:下面的代碼有問題嗎?如果有,如何修改?
1 #include <stdio.h>
2 int* inc(int val)
3 {
4 int a = val;
5 a++;
6 return &a;
7 }
8
9 int main(void)
10 {
11 int a = 10;
12 int *val = inc(a);
13 printf("\n Increamented value is equal to [%d] \n", *val);
14
15 return 0;
16 }
A:雖然上面的代碼有時運行會很好,但是在方法 inc() 中有很嚴重的隱患,因為它返回了局部變��的地址。當inc()方法執行後,再次使用局部變量的地址就會造成不可估量的結果。解決之道就是傳遞變量a的地址給main()。PS:我覺得最後一句的說法有問題。
12 處理printf()參數
Q:請問以下代碼的輸出是什麼?
#include<stdio.h>
int main( void )
{
int a = 10, b = 20, c = 30;
printf ("\n %d..%d..%d \n", a+b+c, (b = b*2), (c = c*2));
return 0;
}
A:程序的輸出如下:
110..40..60
這是因為參數都是從右向左處理的,然後打印出來卻是從左向右。