一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - C/C++ - C 語言編程中的五個常見錯誤及對應解決方案

C 語言編程中的五個常見錯誤及對應解決方案

2021-10-19 20:52Linux中國 LCTT unigeorge 譯 C/C++

C 語言有時名聲不太好,因為它不像近期的編程語言(比如 Rust)那樣具有內存安全性。但是通過額外的代碼,一些最常見和嚴重的 C 語言錯誤是可以避免的。

即使是最好的程序員也無法完全避免錯誤。這些錯誤可能會引入安全漏洞、導致程序崩潰或產生意外操作,具體影響要取決于程序的運行邏輯。

C 語言有時名聲不太好,因為它不像近期的編程語言(比如 Rust)那樣具有內存安全性。但是通過額外的代碼,一些最常見和嚴重的 C 語言錯誤是可以避免的。下文講解了可能影響應用程序的五個錯誤以及避免它們的方法:

C 語言編程中的五個常見錯誤及對應解決方案

1. 未初始化的變量

程序啟動時,系統會為其分配一塊內存以供存儲數據。這意味著程序啟動時,變量將獲得內存中的一個隨機值。

有些編程環境會在程序啟動時特意將內存“清零”,因此每個變量都得以有初始的零值。程序中的變量都以零值作為初始值,聽上去是很不錯的。但是在 C 編程規范中,系統并不會初始化變量。

看一下這個使用了若干變量和兩個數組的示例程序:

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. int 
  4. main() 
  5.   int i, j, k; 
  6.   int numbers[5]; 
  7.   int *array; 
  8.   puts("These variables are not initialized:"); 
  9.   printf("  i = %d\n", i); 
  10.   printf("  j = %d\n", j); 
  11.   printf("  k = %d\n", k); 
  12.   puts("This array is not initialized:"); 
  13.   for (i = 0; i < 5; i++) { 
  14.     printf("  numbers[%d] = %d\n", i, numbers[i]); 
  15.   } 
  16.   puts("malloc an array ..."); 
  17.   array = malloc(sizeof(int) * 5); 
  18.   if (array) { 
  19.     puts("This malloc'ed array is not initialized:"); 
  20.     for (i = 0; i < 5; i++) { 
  21.       printf("  array[%d] = %d\n", i, array[i]); 
  22.     } 
  23.     free(array); 
  24.   } 
  25.   /* done */ 
  26.   puts("Ok"); 
  27.   return 0; 

這個程序不會初始化變量,所以變量以系統內存中的隨機值作為初始值。在我的 Linux 系統上編譯和運行這個程序,會看到一些變量恰巧有“零”值,但其他變量并沒有:

  1. These variables are not initialized: 
  2.   i = 0 
  3.   j = 0 
  4.   k = 32766 
  5. This array is not initialized: 
  6.   numbers[0] = 0 
  7.   numbers[1] = 0 
  8.   numbers[2] = 4199024 
  9.   numbers[3] = 0 
  10.   numbers[4] = 0 
  11. malloc an array ... 
  12. This malloc'ed array is not initialized: 
  13.   array[0] = 0 
  14.   array[1] = 0 
  15.   array[2] = 0 
  16.   array[3] = 0 
  17.   array[4] = 0 
  18. Ok 

很幸運,i 和 j 變量是從零值開始的,但 k 的起始值為 32766。在 numbers 數組中,大多數元素也恰好從零值開始,只有第三個元素的初始值為 4199024。

在不同的系統上編譯相同的程序,可以進一步顯示未初始化變量的危險性。不要誤以為“全世界都在運行 Linux”,你的程序很可能某天在其他平臺上運行。例如,下面是在 FreeDOS 上運行相同程序的結果:

  1. These variables are not initialized: 
  2.   i = 0 
  3.   j = 1074 
  4.   k = 3120 
  5. This array is not initialized: 
  6.   numbers[0] = 3106 
  7.   numbers[1] = 1224 
  8.   numbers[2] = 784 
  9.   numbers[3] = 2926 
  10.   numbers[4] = 1224 
  11. malloc an array ... 
  12. This malloc'ed array is not initialized: 
  13.   array[0] = 3136 
  14.   array[1] = 3136 
  15.   array[2] = 14499 
  16.   array[3] = -5886 
  17.   array[4] = 219 
  18. Ok 

永遠都要記得初始化程序的變量。如果你想讓變量將以零值作為初始值,請額外添加代碼將零分配給該變量。預先編好這些額外的代碼,這會有助于減少日后讓人頭疼的調試過程。

2. 數組越界

C 語言中,數組索引從零開始。這意味著對于長度為 10 的數組,索引是從 0 到 9;長度為 1000 的數組,索引則是從 0 到 999。

程序員有時會忘記這一點,他們從索引 1 開始引用數組,產生了“大小差一”off by one錯誤。在長度為 5 的數組中,程序員在索引“5”處使用的值,實際上并不是數組的第 5 個元素。相反,它是內存中的一些其他值,根本與此數組無關。

這是一個數組越界的示例程序。該程序使用了一個只含有 5 個元素的數組,但卻引用了該范圍之外的數組元素:

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. int 
  4. main() 
  5.   int i; 
  6.   int numbers[5]; 
  7.   int *array; 
  8.   /* test 1 */ 
  9.   puts("This array has five elements (0 to 4)"); 
  10.   /* initalize the array */ 
  11.   for (i = 0; i < 5; i++) { 
  12.     numbers[i] = i; 
  13.   } 
  14.   /* oops, this goes beyond the array bounds: */ 
  15.   for (i = 0; i < 10; i++) { 
  16.     printf("  numbers[%d] = %d\n", i, numbers[i]); 
  17.   } 
  18.   /* test 2 */ 
  19.   puts("malloc an array ..."); 
  20.   array = malloc(sizeof(int) * 5); 
  21.   if (array) { 
  22.     puts("This malloc'ed array also has five elements (0 to 4)"); 
  23.     /* initalize the array */ 
  24.     for (i = 0; i < 5; i++) { 
  25.       array[i] = i; 
  26.     } 
  27.     /* oops, this goes beyond the array bounds: */ 
  28.     for (i = 0; i < 10; i++) { 
  29.       printf("  array[%d] = %d\n", i, array[i]); 
  30.     } 
  31.     free(array); 
  32.   } 
  33.   /* done */ 
  34.   puts("Ok"); 
  35.   return 0; 

可以看到,程序初始化了數組的所有值(從索引 0 到 4),然后從索引 0 開始讀取,結尾是索引 9 而不是索引 4。前五個值是正確的,再后面的值會讓你不知所以:

  1. This array has five elements (0 to 4) 
  2.   numbers[0] = 0 
  3.   numbers[1] = 1 
  4.   numbers[2] = 2 
  5.   numbers[3] = 3 
  6.   numbers[4] = 4 
  7.   numbers[5] = 0 
  8.   numbers[6] = 4198512 
  9.   numbers[7] = 0 
  10.   numbers[8] = 1326609712 
  11.   numbers[9] = 32764 
  12. malloc an array ... 
  13. This malloc'ed array also has five elements (0 to 4) 
  14.   array[0] = 0 
  15.   array[1] = 1 
  16.   array[2] = 2 
  17.   array[3] = 3 
  18.   array[4] = 4 
  19.   array[5] = 0 
  20.   array[6] = 133441 
  21.   array[7] = 0 
  22.   array[8] = 0 
  23.   array[9] = 0 
  24. Ok 

引用數組時,始終要記得追蹤數組大小。將數組大小存儲在變量中;不要對數組大小進行硬編碼hard-code。否則,如果后期該標識符指向另一個不同大小的數組,卻忘記更改硬編碼的數組長度時,程序就可能會發生數組越界。

3. 字符串溢出

字符串只是特定類型的數組。在 C 語言中,字符串是一個由 char 類型值組成的數組,其中用一個零字符表示字符串的結尾。

因此,與數組一樣,要注意避免超出字符串的范圍。有時也稱之為 字符串溢出。

使用 gets 函數讀取數據是一種很容易發生字符串溢出的行為方式。gets 函數非常危險,因為它不知道在一個字符串中可以存儲多少數據,只會機械地從用戶那里讀取數據。如果用戶輸入像 foo 這樣的短字符串,不會發生意外;但是當用戶輸入的值超過字符串長度時,后果可能是災難性的。

下面是一個使用 gets 函數讀取城市名稱的示例程序。在這個程序中,我還添加了一些未使用的變量,來展示字符串溢出對其他數據的影響:

  1. #include <stdio.h> 
  2. #include <string.h> 
  3. int 
  4. main() 
  5.   char name[10];                       /* Such as "Chicago" */ 
  6.   int var1 = 1, var2 = 2; 
  7.   /* show initial values */ 
  8.   printf("var1 = %d; var2 = %d\n", var1, var2); 
  9.   /* this is bad .. please don't use gets */ 
  10.   puts("Where do you live?"); 
  11.   gets(name); 
  12.   /* show ending values */ 
  13.   printf("<%s> is length %d\n", name, strlen(name)); 
  14.   printf("var1 = %d; var2 = %d\n", var1, var2); 
  15.   /* done */ 
  16.   puts("Ok"); 
  17.   return 0; 

當你測試類似的短城市名稱時,該程序運行良好,例如伊利諾伊州的 Chicago 或北卡羅來納州的Raleigh:

  1. var1 = 1; var2 = 2 
  2. Where do you live? 
  3. Raleigh 
  4. <Raleigh> is length 7 
  5. var1 = 1; var2 = 2 
  6. Ok 

威爾士的小鎮 Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch 有著世界上最長的名字之一。這個字符串有 58 個字符,遠遠超出了 name 變量中保留的 10 個字符。結果,程序將值存儲在內存的其他區域,覆蓋了 var1 和 var2 的值:

  1. var1 = 1; var2 = 2 
  2. Where do you live? 
  3. Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch 
  4. <Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch> is length 58 
  5. var1 = 2036821625var2 = 2003266668 
  6. Ok 
  7. Segmentation fault (core dumped) 

在運行結束之前,程序會用長字符串覆蓋內存的其他部分區域。注意,var1 和 var2 的值不再是起始的 1 和 2。

避免使用 gets 函數,改用更安全的方法來讀取用戶數據。例如,getline 函數會分配足夠的內存來存儲用戶輸入,因此不會因輸入長值而發生意外的字符串溢出。

4. 重復釋放內存

“分配的內存要手動釋放”是良好的 C 語言編程原則之一。程序可以使用 malloc 函數為數組和字符串分配內存,該函數會開辟一塊內存,并返回一個指向內存中起始地址的指針。之后,程序可以使用 free 函數釋放內存,該函數會使用指針將內存標記為未使用。

但是,你應該只使用一次 free 函數。第二次調用 free 會導致意外的后果,可能會毀掉你的程序。下面是一個針對此點的簡短示例程序。程序分配了內存,然后立即釋放了它。但為了模仿一個健忘但有條理的程序員,我在程序結束時又一次釋放了內存,導致兩次釋放了相同的內存:

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. int 
  4. main() 
  5.   int *array; 
  6.   puts("malloc an array ..."); 
  7.   array = malloc(sizeof(int) * 5); 
  8.   if (array) { 
  9.     puts("malloc succeeded"); 
  10.     puts("Free the array..."); 
  11.     free(array); 
  12.   } 
  13.   puts("Free the array..."); 
  14.   free(array); 
  15.   puts("Ok"); 

運行這個程序會導致第二次使用 free 函數時出現戲劇性的失敗:

  1. malloc an array ... 
  2. malloc succeeded 
  3. Free the array... 
  4. Free the array... 
  5. free(): double free detected in tcache 2 
  6. Aborted (core dumped) 

要記得避免在數組或字符串上多次調用 free。將 malloc 和 free 函數定位在同一個函數中,這是避免重復釋放內存的一種方法。

例如,一個紙牌游戲程序可能會在主函數中為一副牌分配內存,然后在其他函數中使用這副牌來玩游戲。記得在主函數,而不是其他函數中釋放內存。將 malloc 和 free 語句放在一起有助于避免多次釋放內存。

5. 使用無效的文件指針

文件是一種便捷的數據存儲方式。例如,你可以將程序的配置數據存儲在 config.dat 文件中。Bash shell 會從用戶家目錄中的 .bash_profile 讀取初始化腳本。GNU Emacs 編輯器會尋找文件 .emacs 以從中確定起始值。而 Zoom 會議客戶端使用 zoomus.conf 文件讀取其程序配置。

所以,從文件中讀取數據的能力幾乎對所有程序都很重要。但是假如要讀取的文件不存在,會發生什么呢?

在 C 語言中讀取文件,首先要用 fopen 函數打開文件,該函數會返回指向文件的流指針。你可以結合其他函數,使用這個指針來讀取數據,例如 fgetc 會逐個字符地讀取文件。

如果要讀取的文件不存在或程序沒有讀取權限,fopen 函數會返回 NULL 作為文件指針,這表示文件指針無效。但是這里有一個示例程序,它機械地直接去讀取文件,不檢查 fopen 是否返回了 NULL:

  1. #include <stdio.h> 
  2. int 
  3. main() 
  4.   FILE *pfile; 
  5.   int ch; 
  6.   puts("Open the FILE.TXT file ..."); 
  7.   pfile = fopen("FILE.TXT", "r"); 
  8.   /* you should check if the file pointer is valid, but we skipped that */ 
  9.   puts("Now display the contents of FILE.TXT ..."); 
  10.   while ((ch = fgetc(pfile)) != EOF) { 
  11.     printf("<%c>", ch); 
  12.   } 
  13.   fclose(pfile); 
  14.   /* done */ 
  15.   puts("Ok"); 
  16.   return 0; 

當你運行這個程序時,第一次調用 fgetc 會失敗,程序會立即中止:

  1. Open the FILE.TXT file ... 
  2. Now display the contents of FILE.TXT ... 
  3. Segmentation fault (core dumped) 

始終檢查文件指針以確保其有效。例如,在調用 fopen 打開一個文件后,用類似 if (pfile != NULL) 的語句檢查指針,以確保指針是可以使用的。

人都會犯錯,最優秀的程序員也會產生編程錯誤。但是,遵循上面這些準則,添加一些額外的代碼來檢查這五種類型的錯誤,就可以避免最嚴重的 C 語言編程錯誤。提前編寫幾行代碼來捕獲這些錯誤,可能會幫你節省數小時的調試時間。

原文地址:https://linux.cn/article-13894-1.html

延伸 · 閱讀

精彩推薦
  • C/C++C++之重載 重定義與重寫用法詳解

    C++之重載 重定義與重寫用法詳解

    這篇文章主要介紹了C++之重載 重定義與重寫用法詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下...

    青山的青6062022-01-04
  • C/C++學習C++編程的必備軟件

    學習C++編程的必備軟件

    本文給大家分享的是作者在學習使用C++進行編程的時候所用到的一些常用的軟件,這里推薦給大家...

    謝恩銘10102021-05-08
  • C/C++c++ 單線程實現同時監聽多個端口

    c++ 單線程實現同時監聽多個端口

    這篇文章主要介紹了c++ 單線程實現同時監聽多個端口的方法,幫助大家更好的理解和學習使用c++,感興趣的朋友可以了解下...

    源之緣11542021-10-27
  • C/C++深入理解goto語句的替代實現方式分析

    深入理解goto語句的替代實現方式分析

    本篇文章是對goto語句的替代實現方式進行了詳細的分析介紹,需要的朋友參考下...

    C語言教程網7342020-12-03
  • C/C++C語言中炫酷的文件操作實例詳解

    C語言中炫酷的文件操作實例詳解

    內存中的數據都是暫時的,當程序結束時,它們都將丟失,為了永久性的保存大量的數據,C語言提供了對文件的操作,這篇文章主要給大家介紹了關于C語言中文件...

    針眼_6702022-01-24
  • C/C++詳解c語言中的 strcpy和strncpy字符串函數使用

    詳解c語言中的 strcpy和strncpy字符串函數使用

    strcpy 和strcnpy函數是字符串復制函數。接下來通過本文給大家介紹c語言中的strcpy和strncpy字符串函數使用,感興趣的朋友跟隨小編要求看看吧...

    spring-go5642021-07-02
  • C/C++C語言實現電腦關機程序

    C語言實現電腦關機程序

    這篇文章主要為大家詳細介紹了C語言實現電腦關機程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    xiaocaidayong8482021-08-20
  • C/C++C/C++經典實例之模擬計算器示例代碼

    C/C++經典實例之模擬計算器示例代碼

    最近在看到的一個需求,本以為比較簡單,但花了不少時間,所以下面這篇文章主要給大家介紹了關于C/C++經典實例之模擬計算器的相關資料,文中通過示...

    jia150610152021-06-07
主站蜘蛛池模板: 九九热免费在线观看 | 国色天香社区在线视频播放 | 情缘免费观看完整版 | 精品亚洲视频在线观看 | 日韩一品在线播放视频一品免费 | 日韩精品一二三区 | 欧美成人v视频免费看 | 91真人毛片一级在线播放 | 久久91精品国产91久 | 91久久精品国产亚洲 | 啊用力好大粗黑人小说 | 成人在线观看一区 | 99久久国产综合精品网成人影院 | www.久久av.com| 国产成人精品系列在线观看 | 大伊人青草狠狠久久 | 欧美bbb人妖 | 午夜精品久久久久久久99蜜桃i | 视频在线观看入口一二三2021 | 男人的天堂欧美 | 久久人妻少妇嫩草AV無碼 | 久青草国产在视频在线观看 | 高h禁伦奶水女 | 日本精品一区二区三区 | 特黄特色大片免费视频播放 | 久久精品国产亚洲AV天美18 | 亚洲福利一区二区三区 | 四虎精品成人a在线观看 | 98在线视频噜噜噜国产 | 人人人人人看碰人人免费 | 国产一级一级一级成人毛片 | 国产一卡2卡3卡四卡精品网 | 母性本能在线观看 | 91短视频社区在线观看 | 韩剧消失的眼角膜免费完整版 | 四虎伊人| 2021国产精品露脸在线 | 亚洲高清在线视频 | 精品久久免费观看 | 九九热视频免费观看 | 欧美视频一级 |