這篇文章涉及到的都是一些C語言的基礎知識,是從一篇英文文章中部分翻譯和加入我自己的認識!!!!
1. gets()函數
Q:下面的代碼中隱含着安全問題,能發現嗎?
1 #include<stdio.h> 2 int main(void) 3 { 4 char buff[10]; 5 memset(buff,0,sizeof(buff)); 6 7 gets(buff); 8 9 printf("\n The buffer entered is [%s]\n",buff); 10 11 return 0; 12 }
A:問題在於gets()函數,這個函數是接收標准輸入的一串字符串,並且沒有檢查字符串緩沖區的大小就
直接拷貝到buff數組中,這可能導致在寫入buff內存時溢出,可以使用fgets()函數代替這個函數,
char *fgets( char *str,int n,FILE *stream );
2.strcpy()函數
Q:下面代碼是一個密碼驗證的過程,是否可以在不知道密碼的情況下驗證通過
1 #include<stdio.h> 2 #include <string.h> 3 4 int main(int argc, char *argv[]) 5 { 6 7 char passwd[10]; 8 int flag = 0; 9 memset(passwd,0,sizeof(passwd)); 10 11 strcpy(passwd, argv[1]); 12 13 if(0 == strcmp("LinuxGeek", passwd)) 14 { 15 flag = 1; 16 } 17 18 if(flag) 19 { 20 printf("\n Password cracked \n"); 21 } 22 else 23 { 24 printf("\n Incorrect passwd \n"); 25 26 } 27 return 0; 28 }
A:strcpy()函數沒有驗證輸入的字符串長度,所有在執行時可能出現寫內存出現溢出,這很危險,如這代碼,
flag是初始化為0的,當內存溢出時可能會寫到flag內存中,這會使得flag內存不為0,即使不執行if語句的
比較flag也為真,所以就相當於密碼正確
如: $ ./psswd aaaaaaaaaaaaa 輸出: Password cracked
可以使用strncpy()函數代替
現在編譯器也發現這種情況,所以在為程序分配內存時是分散的分配內存,如果要看到上面執行的情況,使用gcc的話
可以使用 ‘-fno-stack-protector’參數(我沒驗證)
3.main()函數的返回類型
Q:下面代碼是否能編譯通過?是否還存在其他問題
1 #include<stdio.h> 2 3 void main(void) 4 { 5 char *ptr = (char*)malloc(10); 6 7 if(NULL == ptr) 8 { 9 printf("\n Malloc failed \n"); 10 return; 11 } 12 else 13 { 14 // Do some processing 15 16 free(ptr); 17 } 18 19 return; 20 }
A:對於現在的編譯器這段代碼是可以編譯通過的,不過是會有警告,main()返回類型最好使用int類型,
當一個函數執行結束時最后返回一個狀態值,現在C/C++返回一個0值表示程序正常退出,否則有異常.
4.內存泄露
Q:下面代碼執行結果會出現內存泄露嗎?
1 #include<stdio.h> 2 3 void main(void) 4 { 5 char *ptr = (char*)malloc(10); 6 7 if(NULL == ptr) 8 { 9 printf("\n Malloc failed \n"); 10 return; 11 } 12 else 13 { 14 // Do some processing 15 } 16 17 return; 18 }
A:其實內存泄露是個很嚴重的問題,其實上面代碼執行結果不會出現內存泄露,雖然沒有使用free()
回收內存,但是當程序執行結束后程序里分配的內存會自動釋放,如果是分配的內存放到一個死循環里
就會出現嚴重的內存泄露,或者程序一直執行着,動態分配的內存會一直占有着無法釋放.
有篇文章介紹了內存泄露的檢測方法:http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html
5.free()函數
Q:下面代碼執行時輸入如 ‘freeze’會崩潰但輸入如t ‘zebra’就不會為什么?
1 #include<stdio.h> 2 3 int main(int argc, char *argv[]) 4 { 5 char *ptr = (char*)malloc(10); 6 7 if(NULL == ptr) 8 { 9 printf("\n Malloc failed \n"); 10 return -1; 11 } 12 else if(argc == 1) 13 { 14 printf("\n Usage \n"); 15 } 16 else 17 { 18 memset(ptr, 0, 10); 19 20 strncpy(ptr, argv[1], 9); 21 22 while(*ptr != 'z') 23 { 24 if(*ptr == '') 25 break; 26 else 27 ptr++; 28 } 29 30 if(*ptr == 'z') 31 { 32 printf("\n String contains 'z'\n"); 33 // Do some more processing 34 } 35 36 free(ptr); 37 } 38 39 return 0; 40 }
A:這個問題主要是指針移到的問題,當輸入如‘zebra’這樣的字符串('z'開頭)時,ptr指針沒有移到,所以
ptr指針指向的內存還是malloc分配的原內存的起始地址,但是輸入‘freeze’時,ptr指針移到了,已經不是指向原來
分配的內存的起始地址了,所有free時就會出錯
題外話:在實現如strcpy的函數時,如下
1 char *strcpy(char *strDest, const char *strSrc) 2 { 3 assert((strDest != NULL) && (strSrc != NULL)); 4 5 if(strDest == strSrc) 6 return strDest; //注意這個.. 7 8 char *pstr = strDest; //保存原始地址 9 while((*strDest++ = *strSrc++) != '\0'); 10 return pstr; 11 }
定義了一個指針pstr保存了原始地址,然后再執行移到操作,結束后再返回寫入的原始地址以便進行鏈式操作,
我實現這個函數的時候就是出現了一個很二的錯誤,就是移到操作后直接返回strDest指針,現在的strDest指針
已經移到字符串末尾了,如果那樣的話要釋放strDest內存就會出錯了
6.atexit和_exit
Q:下面代碼中,atexit()函數沒有被調用,why?
1 #include<stdio.h> 2 void func(void) 3 { 4 printf("\n Cleanup function called \n"); 5 return; 6 } 7 8 int main(void) 9 { 10 int i = 0; 11 12 atexit(func); 13 14 for(;i<0xffffff;i++); 15 16 _exit(0); 17 }
A:主要是因為_exit()函數,這個函數是直接終止程序,沒有調用一些如atexit()的清理函數,要想調用
atexit()函數,就需要調用exit()函數或者return返回操作退出程序.
MSDN給出了exit()和_exit()函數的區別
The exit and _exit functions terminate the calling process. exit calls, in last-in-first-out (LIFO) order,
the functions registered by atexit and _onexit, then flushes all file buffers before terminating the process. _exit terminates the process without processing atexit or _onexit or flushing stream buffers. The status value is typically set to 0 to indicate a normal exit and set to some other value to indicate an error
7.void* 和 C 結構體
Q:能否設計一個函數能夠接受任何類型的參數? 也可以傳遞多個參數給這個函數?
A:
int func(void *ptr);
傳遞的時候需要強制轉換為void*類型,到函數內以后再強制轉換回來,如果要傳多個參數,可以定義一個
結構體,將要傳遞的參數放到結構體里,定義一個結構體對象,對成員賦值后,將對象傳給函數.
注:我不知道在哪里看到有人說C/C++是不安全的語言,因為可以類型強制轉換,但在這里發現強制轉換
帶來的好處.
8. * 和 ++ 操作
Q:下面的代碼輸出的結果是什么? and Why?
1 #include<stdio.h> 2 3 int main(void) 4 { 5 char *ptr = "Linux"; 6 printf("\n [%c] \n",*ptr++); 7 printf("\n [%c] \n",*ptr); 8 9 return 0; 10 }
A:輸入的結果:
[L]
[i]
Why? 這個涉及到*號和++的優先級問題,*和++的優先級是一樣的,編譯器在識別時是先右后左,
所有先檢測到++后到*,不過++是在ptr后面,所以自加在*ptr后執行,輸出L,然后移動到i處,下
一條語句執行的時候輸出i.
原作者給出來的解釋我不太理解
(ptr++ is evaluated first and then *ptr. So both these operations result in ‘L’),
為什么說兩個操作結果都是'L'???
9.改變代碼區(只讀區)
Q:下面代碼為什么會崩潰?
1 #include<stdio.h> 2 3 int main(void) 4 { 5 char *ptr = "Linux"; 6 *ptr = 'T'; 7 8 printf("\n [%s] \n", ptr); 9 10 return 0; 11 }
A:因為 *ptr = 'T' 操作嘗試改變在代碼區的 "Linux"的字符串,這是不合法的,其實如果要改變的話
可以先動態分配一塊內存(在堆區),然后再Copy "Linux"到這塊內存中,可以就可以執行*ptr = 'T'操作
10.程序改變自己的名字
Q:如何實現一個程序在運行的時候改變自己的名字
A:這個需要知道main()函數的兩個參數的意義了
1 #include<stdio.h> 2 #include <string.h> 3 int main(int argc, char *argv[]) 4 { 5 int i = 0; 6 char buff[100]; 7 8 memset(buff,0,sizeof(buff)); 9 10 strncpy(buff, argv[0], sizeof(buff)); 11 memset(argv[0],0,strlen(buff)); 12 13 strncpy(argv[0], "NewName", 7); 14 15 // Simulate a wait. Check the process 16 // name at this point. 17 for(;i<0xffffffff;i++); 18 19 return 0; 20 }
main()函數的argc表示的是傳進來的參數個數,argv[]保存的是參數的內容,但是
argv[0]保存的是程序自己名字
11.返回局部變量地址
Q:下面的代碼是否存在問題?如果有的話那如何修改?
1 #include<stdio.h> 2 3 int* inc(int val) 4 { 5 int a = val; 6 a++; 7 return &a; 8 } 9 10 int main(void) 11 { 12 int a = 10; 13 14 int *val = inc(a); 15 16 printf("\n Incremented value is equal to [%d] \n", *val); 17 18 return 0; 19 }
A:問題是返回了一個局部變量的地址,a的作用域只在inc()函數中,函數結束后a的內存會釋放,如果使用
一個已經被釋放了的內存相當危險,解決方法可以傳給inc()函數的參數修改為傳地址或者引用(C++)
不使用值傳遞,int* inc(int *val)或者int* inc(int &val)
12.printf()函數參數的執行
Q:代碼的輸出結果是什么?
1 #include<stdio.h> 2 3 int main(void) 4 { 5 int a = 10, b = 20, c = 30; 6 7 printf("\n %d..%d..%d \n", a+b+c, (b = b*2), (c = c*2)); 8 9 return 0; 10 }
A:輸出的結果為
110..40..60
函數的參數是從右到左執行的,但是打印是從左到右的
這篇文章加入了我的一些表述,可能存在不足或者不對的表達,如有興趣可以讀原作者的英文文章