當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > C語(yǔ)言與CPP編程
[導(dǎo)讀]?導(dǎo)讀:增強(qiáng)C語(yǔ)言程序的彈性和可靠性的五種方法?!                  ”疚淖?jǐn)?shù):8391,閱讀時(shí)長(zhǎng)大約:10分鐘https://linux.cn/article-13894-1.html作者:JimHall譯者:unigeorge即使是最好的程序員也無(wú)法完全避免錯(cuò)...

導(dǎo)讀:增強(qiáng) C 語(yǔ)言程序的彈性和可靠性的五種方法。                                     本文字?jǐn)?shù):8391,閱讀時(shí)長(zhǎng)大約:10分鐘
https://linux.cn/article-13894-1.html
作者:Jim Hall
譯者:unigeorge
即使是最好的程序員也無(wú)法完全避免錯(cuò)誤。這些錯(cuò)誤可能會(huì)引入安全漏洞、導(dǎo)致程序崩潰或產(chǎn)生意外操作,具體影響要取決于程序的運(yùn)行邏輯。


C 語(yǔ)言有時(shí)名聲不太好,因?yàn)樗幌窠诘木幊陶Z(yǔ)言(比如 Rust)那樣具有內(nèi)存安全性。但是通過(guò)額外的代碼,一些最常見(jiàn)和嚴(yán)重的 C 語(yǔ)言錯(cuò)誤是可以避免的。下文講解了可能影響應(yīng)用程序的五個(gè)錯(cuò)誤以及避免它們的方法:


1、未初始化的變量


程序啟動(dòng)時(shí),系統(tǒng)會(huì)為其分配一塊內(nèi)存以供存儲(chǔ)數(shù)據(jù)。這意味著程序啟動(dòng)時(shí),變量將獲得內(nèi)存中的一個(gè)隨機(jī)值。


有些編程環(huán)境會(huì)在程序啟動(dòng)時(shí)特意將內(nèi)存“清零”,因此每個(gè)變量都得以有初始的零值。程序中的變量都以零值作為初始值,聽(tīng)上去是很不錯(cuò)的。但是在 C 編程規(guī)范中,系統(tǒng)并不會(huì)初始化變量。


看一下這個(gè)使用了若干變量和兩個(gè)數(shù)組的示例程序:


  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int
  4. main()
  5. {
  6. int i, j, k;
  7. int numbers[5];
  8. int *array;
  9. puts("These variables are not initialized:");
  10. printf(" i = %d\n", i);
  11. printf(" j = %d\n", j);
  12. printf(" k = %d\n", k);
  13. puts("This array is not initialized:");
  14. for (i = 0; i < 5; i ) {
  15. printf(" numbers[%d] = %d\n", i, numbers[i]);
  16. }
  17. puts("malloc an array ...");
  18. array = malloc(sizeof(int) * 5);
  19. if (array) {
  20. puts("This malloc'ed array is not initialized:");
  21. for (i = 0; i < 5; i ) {
  22. printf(" array[%d] = %d\n", i, array[i]);
  23. }
  24. free(array);
  25. }
  26. /* done */
  27. puts("Ok");
  28. return 0;
  29. }
這個(gè)程序不會(huì)初始化變量,所以變量以系統(tǒng)內(nèi)存中的隨機(jī)值作為初始值。在我的 Linux 系統(tǒng)上編譯和運(yùn)行這個(gè)程序,會(huì)看到一些變量恰巧有“零”值,但其他變量并沒(méi)有:


  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
很幸運(yùn),i和j變量是從零值開(kāi)始的,但k的起始值為 32766。在numbers數(shù)組中,大多數(shù)元素也恰好從零值開(kāi)始,只有第三個(gè)元素的初始值為 4199024。


在不同的系統(tǒng)上編譯相同的程序,可以進(jìn)一步顯示未初始化變量的危險(xiǎn)性。不要誤以為“全世界都在運(yùn)行 Linux”,你的程序很可能某天在其他平臺(tái)上運(yùn)行。例如,下面是在 FreeDOS 上運(yùn)行相同程序的結(jié)果:


  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
永遠(yuǎn)都要記得初始化程序的變量。如果你想讓變量將以零值作為初始值,請(qǐng)額外添加代碼將零分配給該變量。預(yù)先編好這些額外的代碼,這會(huì)有助于減少日后讓人頭疼的調(diào)試過(guò)程。


2、數(shù)組越界


C 語(yǔ)言中,數(shù)組索引從零開(kāi)始。這意味著對(duì)于長(zhǎng)度為 10 的數(shù)組,索引是從 0 到 9;長(zhǎng)度為 1000 的數(shù)組,索引則是從 0 到 999。


程序員有時(shí)會(huì)忘記這一點(diǎn),他們從索引 1 開(kāi)始引用數(shù)組,產(chǎn)生了“大小差一”(off by one)錯(cuò)誤。在長(zhǎng)度為 5 的數(shù)組中,程序員在索引“5”處使用的值,實(shí)際上并不是數(shù)組的第 5 個(gè)元素。相反,它是內(nèi)存中的一些其他值,根本與此數(shù)組無(wú)關(guān)。


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


  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int
  4. main()
  5. {
  6. int i;
  7. int numbers[5];
  8. int *array;
  9. /* test 1 */
  10. puts("This array has five elements (0 to 4)");
  11. /* initalize the array */
  12. for (i = 0; i < 5; i ) {
  13. numbers[i] = i;
  14. }
  15. /* oops, this goes beyond the array bounds: */
  16. for (i = 0; i < 10; i ) {
  17. printf(" numbers[%d] = %d\n", i, numbers[i]);
  18. }
  19. /* test 2 */
  20. puts("malloc an array ...");
  21. array = malloc(sizeof(int) * 5);
  22. if (array) {
  23. puts("This malloc'ed array also has five elements (0 to 4)");
  24. /* initalize the array */
  25. for (i = 0; i < 5; i ) {
  26. array[i] = i;
  27. }
  28. /* oops, this goes beyond the array bounds: */
  29. for (i = 0; i < 10; i ) {
  30. printf(" array[%d] = %d\n", i, array[i]);
  31. }
  32. free(array);
  33. }
  34. /* done */
  35. puts("Ok");
  36. return 0;
  37. }
可以看到,程序初始化了數(shù)組的所有值(從索引 0 到 4),然后從索引 0 開(kāi)始讀取,結(jié)尾是索引 9 而不是索引 4。前五個(gè)值是正確的,再后面的值會(huì)讓你不知所以:


  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
引用數(shù)組時(shí),始終要記得追蹤數(shù)組大小。將數(shù)組大小存儲(chǔ)在變量中;不要對(duì)數(shù)組大小進(jìn)行硬編碼(hard-code)。否則,如果后期該標(biāo)識(shí)符指向另一個(gè)不同大小的數(shù)組,卻忘記更改硬編碼的數(shù)組長(zhǎng)度時(shí),程序就可能會(huì)發(fā)生數(shù)組越界。


3、字符串溢出


字符串只是特定類(lèi)型的數(shù)組。在 C 語(yǔ)言中,字符串是一個(gè)由char類(lèi)型值組成的數(shù)組,其中用一個(gè)零字符表示字符串的結(jié)尾。


因此,與數(shù)組一樣,要注意避免超出字符串的范圍。有時(shí)也稱(chēng)之為 字符串溢出


使用gets函數(shù)讀取數(shù)據(jù)是一種很容易發(fā)生字符串溢出的行為方式。gets函數(shù)非常危險(xiǎn),因?yàn)樗恢涝谝粋€(gè)字符串中可以存儲(chǔ)多少數(shù)據(jù),只會(huì)機(jī)械地從用戶那里讀取數(shù)據(jù)。如果用戶輸入像foo這樣的短字符串,不會(huì)發(fā)生意外;但是當(dāng)用戶輸入的值超過(guò)字符串長(zhǎng)度時(shí),后果可能是災(zāi)難性的。


下面是一個(gè)使用gets函數(shù)讀取城市名稱(chēng)的示例程序。在這個(gè)程序中,我還添加了一些未使用的變量,來(lái)展示字符串溢出對(duì)其他數(shù)據(jù)的影響:


  1. #include <stdio.h>
  2. #include <string.h>
  3. int
  4. main()
  5. {
  6. char name[10]; /* Such as "Chicago" */
  7. int var1 = 1, var2 = 2;
  8. /* show initial values */
  9. printf("var1 = %d; var2 = %d\n", var1, var2);
  10. /* this is bad .. please don't use gets */
  11. puts("Where do you live?");
  12. gets(name);
  13. /* show ending values */
  14. printf("<%s> is length %d\n", name, strlen(name));
  15. printf("var1 = %d; var2 = %d\n", var1, var2);
  16. /* done */
  17. puts("Ok");
  18. return 0;
  19. }
當(dāng)你測(cè)試類(lèi)似的短城市名稱(chēng)時(shí),該程序運(yùn)行良好,例如伊利諾伊州的Chicago或北卡羅來(lái)納州的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
威爾士的小鎮(zhèn)Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch有著世界上最長(zhǎng)的名字之一。這個(gè)字符串有 58 個(gè)字符,遠(yuǎn)遠(yuǎn)超出了name變量中保留的 10 個(gè)字符。結(jié)果,程序?qū)⒅荡鎯?chǔ)在內(nèi)存的其他區(qū)域,覆蓋了var1和var2的值:


  1. var1 = 1; var2 = 2
  2. Where do you live?
  3. Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch
  4. <Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch> is length 58
  5. var1 = 2036821625; var2 = 2003266668
  6. Ok
  7. Segmentation fault (core dumped)
在運(yùn)行結(jié)束之前,程序會(huì)用長(zhǎng)字符串覆蓋內(nèi)存的其他部分區(qū)域。注意,var1和var2的值不再是起始的1和2。


避免使用gets函數(shù),改用更安全的方法來(lái)讀取用戶數(shù)據(jù)。例如,getline函數(shù)會(huì)分配足夠的內(nèi)存來(lái)存儲(chǔ)用戶輸入,因此不會(huì)因輸入長(zhǎng)值而發(fā)生意外的字符串溢出。


4、重復(fù)釋放內(nèi)存


“分配的內(nèi)存要手動(dòng)釋放”是良好的 C 語(yǔ)言編程原則之一。程序可以使用malloc函數(shù)為數(shù)組和字符串分配內(nèi)存,該函數(shù)會(huì)開(kāi)辟一塊內(nèi)存,并返回一個(gè)指向內(nèi)存中起始地址的指針。之后,程序可以使用free函數(shù)釋放內(nèi)存,該函數(shù)會(huì)使用指針將內(nèi)存標(biāo)記為未使用。


但是,你應(yīng)該只使用一次free函數(shù)。第二次調(diào)用free會(huì)導(dǎo)致意外的后果,可能會(huì)毀掉你的程序。下面是一個(gè)針對(duì)此點(diǎn)的簡(jiǎn)短示例程序。程序分配了內(nèi)存,然后立即釋放了它。但為了模仿一個(gè)健忘但有條理的程序員,我在程序結(jié)束時(shí)又一次釋放了內(nèi)存,導(dǎo)致兩次釋放了相同的內(nèi)存:


  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int
  4. main()
  5. {
  6. int *array;
  7. puts("malloc an array ...");
  8. array = malloc(sizeof(int) * 5);
  9. if (array) {
  10. puts("malloc succeeded");
  11. puts("Free the array...");
  12. free(array);
  13. }
  14. puts("Free the array...");
  15. free(array);
  16. puts("Ok");
  17. }
運(yùn)行這個(gè)程序會(huì)導(dǎo)致第二次使用free函數(shù)時(shí)出現(xiàn)戲劇性的失敗:


  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)
要記得避免在數(shù)組或字符串上多次調(diào)用free。將malloc和free函數(shù)定位在同一個(gè)函數(shù)中,這是避免重復(fù)釋放內(nèi)存的一種方法。


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


5、使用無(wú)效的文件指針


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


所以,從文件中讀取數(shù)據(jù)的能力幾乎對(duì)所有程序都很重要。但是假如要讀取的文件不存在,會(huì)發(fā)生什么呢?


在 C 語(yǔ)言中讀取文件,首先要用fopen函數(shù)打開(kāi)文件,該函數(shù)會(huì)返回指向文件的流指針。你可以結(jié)合其他函數(shù),使用這個(gè)指針來(lái)讀取數(shù)據(jù),例如fgetc會(huì)逐個(gè)字符地讀取文件。


如果要讀取的文件不存在或程序沒(méi)有讀取權(quán)限,fopen函數(shù)會(huì)返回NULL作為文件指針,這表示文件指針無(wú)效。但是這里有一個(gè)示例程序,它機(jī)械地直接去讀取文件,不檢查fopen是否返回了NULL:


  1. #include <stdio.h>
  2. int
  3. main()
  4. {
  5. FILE *pfile;
  6. int ch;
  7. puts("Open the FILE.TXT file ...");
  8. pfile = fopen("FILE.TXT", "r");
  9. /* you should check if the file pointer is valid, but we skipped that */
  10. puts("Now display the contents of FILE.TXT ...");
  11. while ((ch = fgetc(pfile)) != EOF) {
  12. printf("<%c>", ch);
  13. }
  14. fclose(pfile);
  15. /* done */
  16. puts("Ok");
  17. return 0;
  18. }
當(dāng)你運(yùn)行這個(gè)程序時(shí),第一次調(diào)用fgetc會(huì)失敗,程序會(huì)立即中止:


  1. Open the FILE.TXT file ...
  2. Now display the contents of FILE.TXT ...
  3. Segmentation fault (core dumped)
始終檢查文件指針以確保其有效。例如,在調(diào)用fopen打開(kāi)一個(gè)文件后,用類(lèi)似if (pfile != NULL)的語(yǔ)句檢查指針,以確保指針是可以使用的。


人都會(huì)犯錯(cuò),最優(yōu)秀的程序員也會(huì)產(chǎn)生編程錯(cuò)誤。但是,遵循上面這些準(zhǔn)則,添加一些額外的代碼來(lái)檢查這五種類(lèi)型的錯(cuò)誤,就可以避免最嚴(yán)重的 C 語(yǔ)言編程錯(cuò)誤。提前編寫(xiě)幾行代碼來(lái)捕獲這些錯(cuò)誤,可能會(huì)幫你節(jié)省數(shù)小時(shí)的調(diào)試時(shí)間。


via: https://opensource.com/article/21/10/programming-bugs


作者:Jim Hall 選題:lujun9972 譯者:unigeorge 校對(duì):wxy


本文由 LCTT 原創(chuàng)編譯



本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專(zhuān)欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

電感是導(dǎo)線內(nèi)通過(guò)交流電流時(shí),在導(dǎo)線的內(nèi)部及其周?chē)a(chǎn)生交變磁通,導(dǎo)線的磁通量與生產(chǎn)此磁通的電流之比。電感器也叫電感線圈,是利用電磁感應(yīng)原理制成的,由導(dǎo)線在絕緣管上單層或多層繞制而成的,導(dǎo)線彼此互相絕緣,而絕緣管可以是空心的...

關(guān)鍵字: 電感 磁通量 電感器

根據(jù)交通運(yùn)輸部水運(yùn)科學(xué)研究院提出的智慧港口的概念,智慧港口是利用新一代信息技術(shù),將港口相關(guān)業(yè)務(wù)和管理創(chuàng)新深度融合,使港口更加集約、高效、便捷、安全、綠色,創(chuàng)新港口發(fā)展模式,實(shí)現(xiàn)港口科學(xué)可持續(xù)發(fā)展。

關(guān)鍵字: 智慧港口 信息技術(shù) 業(yè)務(wù)

近年來(lái),世界主要汽車(chē)大國(guó)紛紛加強(qiáng)新能源汽車(chē)戰(zhàn)略謀劃、強(qiáng)化政策支持、完善產(chǎn)業(yè)布局,新能源汽車(chē)已成為全球汽車(chē)產(chǎn)業(yè)轉(zhuǎn)型發(fā)展的主要方向和促進(jìn)世界經(jīng)濟(jì)持續(xù)增長(zhǎng)的重要引擎。2021年,全國(guó)新能源汽車(chē)實(shí)現(xiàn)產(chǎn)量354.5萬(wàn)輛,銷(xiāo)量352...

關(guān)鍵字: 新能源 汽車(chē) 引擎

2007-2021年,全球針狀焦行業(yè)專(zhuān)利申請(qǐng)人數(shù)量及專(zhuān)利申請(qǐng)量總體呈現(xiàn)增長(zhǎng)態(tài)勢(shì)。雖然2021年全球針狀焦行業(yè)專(zhuān)利申請(qǐng)人數(shù)量及專(zhuān)利申請(qǐng)量有所下降,但是這兩大指標(biāo)數(shù)量仍較多。整體來(lái)看,全球針狀焦技術(shù)處于成長(zhǎng)期。

關(guān)鍵字: 針狀焦行業(yè) 專(zhuān)利申請(qǐng)人 增長(zhǎng)態(tài)勢(shì)

按企業(yè)主營(yíng)業(yè)務(wù)類(lèi)型分,我國(guó)智能家居行業(yè)競(jìng)爭(zhēng)派系可分為傳統(tǒng)家電企業(yè)、互聯(lián)網(wǎng)企業(yè)以及其他企業(yè)三派。傳統(tǒng)家電企業(yè)代表有海爾智家、美的集團(tuán)、格力電器等,具有供應(yīng)鏈和銷(xiāo)售渠道,制造能力和品牌優(yōu)勢(shì)突出;互聯(lián)網(wǎng)企業(yè)代表有小米集團(tuán)、百度...

關(guān)鍵字: 智能家居 互聯(lián)網(wǎng)企業(yè) 供應(yīng)鏈

軍工電子是集紅外技術(shù)、激光技術(shù)、半導(dǎo)體及嵌入式技術(shù)與虛擬仿真技術(shù)為一體的綜合性軍工技術(shù)體系,是國(guó)防信息化建設(shè)的基石。軍工電子行業(yè)包含在軍工行業(yè)內(nèi),專(zhuān)注于軍工行業(yè)電子產(chǎn)品布局。根據(jù)其軍工產(chǎn)品的不同可分為衛(wèi)星導(dǎo)航、通信指揮、...

關(guān)鍵字: 軍工電子 嵌入式技術(shù) 信息化建設(shè)

我國(guó)汽車(chē)零配件行業(yè)細(xì)分種類(lèi)眾多,從汽車(chē)零配件主要產(chǎn)品來(lái)看,發(fā)動(dòng)機(jī)系統(tǒng)行業(yè)內(nèi)有濰柴動(dòng)力、華域汽車(chē)等主要從業(yè)企業(yè);在車(chē)身零部件領(lǐng)域內(nèi),福耀玻璃、中策橡膠具有一定的規(guī)模優(yōu)勢(shì);行駛系統(tǒng)領(lǐng)域內(nèi)有中策橡膠提供的輪胎以及華為等企業(yè)提供...

關(guān)鍵字: 汽車(chē)零配件 發(fā)動(dòng)機(jī) 行駛系統(tǒng)

茶飲料是指以茶葉或茶葉的水提取液、濃縮液、茶粉(包括速溶茶粉、研磨茶粉)或直接以茶的鮮葉為原料添加或不添加食品原輔料和(或)食品添加劑,經(jīng)加工制成的液體飲料。根據(jù)國(guó)家標(biāo)準(zhǔn)《茶飲料(GB/T 21733-2008)》的規(guī)定...

關(guān)鍵字: 茶飲料 茶葉的水 食品添加劑

全球液壓行業(yè)專(zhuān)利技術(shù)在21世紀(jì)初得到初步發(fā)展,這一時(shí)期液壓專(zhuān)利申請(qǐng)人數(shù)量和申請(qǐng)量處于較低水平。2011-2012年,液壓行業(yè)專(zhuān)利技術(shù)的發(fā)展總體處于成長(zhǎng)期,2012年以后中全球液壓行業(yè)專(zhuān)利技術(shù)申請(qǐng)量或申請(qǐng)人數(shù)量整體處于波動(dòng)...

關(guān)鍵字: 液壓行業(yè) 專(zhuān)利授權(quán) 技術(shù)類(lèi)型

從上市企業(yè)的總市值情況來(lái)看,2022年7月28日,中芯國(guó)際、紫光國(guó)微和韋爾股份總市值遙遙領(lǐng)先,中芯國(guó)際總市值達(dá)到3238.21億元,紫光國(guó)微總市值達(dá)到1358.77億元,韋爾股份總市值達(dá)到1277.07億元;其次是兆易創(chuàng)...

關(guān)鍵字: 上市企業(yè) 集成電路 行業(yè)

C語(yǔ)言與CPP編程

252 篇文章

關(guān)注

發(fā)布文章

編輯精選

更多

論壇熱帖

關(guān)閉