程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 程序崩了,咋辦?

程序崩了,咋辦?

編輯:關於C語言

這兩天因為調程序,自己簡單的總結了一下C編程中碰到的內存有關的問題和注意事項。


1. 內存溢出是啥?

舉個棧溢出的例子。所有的在函數內部申請的局部變量都是保存在棧中的。比如:

  1. #include <string.h> 
  2.  
  3. void fn(void) 
  4.     char a[100]; 
  5.     char *p = a; 
  6.     bzero(p, 1000); 
  7.  
  8. int main(int argc, char *argv[]) 
  9.     fn(); 
  10.     return 0; 

這裡,數組a就會保存在棧中。當棧溢出時,最容易出現的問題是返回指針被修改,進而函數返回時會發現返回的代碼段指針錯誤,提示:“stack smashing detected...":

  1. peter@ubuntu-910:~/codes/testspace$ ./testspace  
  2. *** stack smashing detected ***: <unknown> terminated 
  3. ======= Backtrace: ========= 
  4. /lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)[0x2f7008] 
  5. /lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x0)[0x2f6fc0] 
  6. [0x80484b2] 
  7. [0x0] 
  8. ======= Memory map: ======== 
  9. 00215000-00216000 r-xp 00000000 00:00 0          [vdso] 
  10. 00216000-00354000 r-xp 00000000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  11. 00354000-00355000 ---p 0013e000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  12. 00355000-00357000 r--p 0013e000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  13. 00357000-00358000 rw-p 00140000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  14. 00358000-0035b000 rw-p 00000000 00:00 0  
  15. 00c38000-00c4d000 r-xp 00000000 08:07 5220       /lib/tls/i686/cmov/libpthread-2.10.1.so 
  16. 00c4d000-00c4e000 r--p 00014000 08:07 5220       /lib/tls/i686/cmov/libpthread-2.10.1.so 
  17. 00c4e000-00c4f000 rw-p 00015000 08:07 5220       /lib/tls/i686/cmov/libpthread-2.10.1.so 
  18. 00c4f000-00c51000 rw-p 00000000 00:00 0  
  19. 00cfc000-00d18000 r-xp 00000000 08:07 4652       /lib/libgcc_s.so.1 
  20. 00d18000-00d19000 r--p 0001b000 08:07 4652       /lib/libgcc_s.so.1 
  21. 00d19000-00d1a000 rw-p 0001c000 08:07 4652       /lib/libgcc_s.so.1 
  22. 00f63000-00f7e000 r-xp 00000000 08:07 5168       /lib/ld-2.10.1.so 
  23. 00f7e000-00f7f000 r--p 0001a000 08:07 5168       /lib/ld-2.10.1.so 
  24. 00f7f000-00f80000 rw-p 0001b000 08:07 5168       /lib/ld-2.10.1.so 
  25. 08048000-08049000 r-xp 00000000 08:08 264941     /home/peter/codes/testspace/testspace 
  26. 08049000-0804a000 r--p 00000000 08:08 264941     /home/peter/codes/testspace/testspace 
  27. 0804a000-0804b000 rw-p 00001000 08:08 264941     /home/peter/codes/testspace/testspace 
  28. 08a74000-08a95000 rw-p 00000000 00:00 0          [heap] 
  29. b785e000-b7860000 rw-p 00000000 00:00 0  
  30. b7874000-b7876000 rw-p 00000000 00:00 0  
  31. bffad000-bffc2000 rw-p 00000000 00:00 0          [stack] 
  32. 已放棄 

這類問題其實比較簡單,起碼在linux系統中,在程序崩潰的同時,系統往往會打印出一些backtrace和memory map之類的東西,其中backtrace可以非常有效的讓我們發現棧溢出發生的函數位置。如果函數比較深比如我們這種情況),或者系統沒有打印bt的信息,而是直接段錯誤了,可以用gdb跟蹤,然後用backtrace命令看:

  1. peter@ubuntu-910:~/codes/testspace$ gdb 
  2. GNU gdb (GDB) 7.0-ubuntu 
  3. Copyright (C) 2009 Free Software Foundation, Inc. 
  4. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> 
  5. This is free software: you are free to change and redistribute it. 
  6. There is NO WARRANTY, to the extent permitted by law.  Type "show copying" 
  7. and "show warranty" for details. 
  8. This GDB was configured as "i486-linux-gnu". 
  9. For bug reporting instructions, please see: 
  10. <http://www.gnu.org/software/gdb/bugs/>. 
  11. (gdb) file testspace  
  12. Reading symbols from /home/peter/codes/testspace/testspace...done. 
  13. (gdb) r 
  14. Starting program: /home/peter/codes/testspace/testspace  
  15. [Thread debugging using libthread_db enabled] 
  16. *** stack smashing detected ***: <unknown> terminated 
  17. ======= Backtrace: ========= 
  18. /lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)[0x228008] 
  19. /lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x0)[0x227fc0] 
  20. [0x80484b2] 
  21. [0x0] 
  22. ======= Memory map: ======== 
  23. 00110000-0012b000 r-xp 00000000 08:07 5168       /lib/ld-2.10.1.so 
  24. 0012b000-0012c000 r--p 0001a000 08:07 5168       /lib/ld-2.10.1.so 
  25. 0012c000-0012d000 rw-p 0001b000 08:07 5168       /lib/ld-2.10.1.so 
  26. 0012d000-0012e000 r-xp 00000000 00:00 0          [vdso] 
  27. 0012e000-00143000 r-xp 00000000 08:07 5220       /lib/tls/i686/cmov/libpthread-2.10.1.so 
  28. 00143000-00144000 r--p 00014000 08:07 5220       /lib/tls/i686/cmov/libpthread-2.10.1.so 
  29. 00144000-00145000 rw-p 00015000 08:07 5220       /lib/tls/i686/cmov/libpthread-2.10.1.so 
  30. 00145000-00147000 rw-p 00000000 00:00 0  
  31. 00147000-00285000 r-xp 00000000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  32. 00285000-00286000 ---p 0013e000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  33. 00286000-00288000 r--p 0013e000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  34. 00288000-00289000 rw-p 00140000 08:07 5206       /lib/tls/i686/cmov/libc-2.10.1.so 
  35. 00289000-0028c000 rw-p 00000000 00:00 0  
  36. 0028c000-002a8000 r-xp 00000000 08:07 4652       /lib/libgcc_s.so.1 
  37. 002a8000-002a9000 r--p 0001b000 08:07 4652       /lib/libgcc_s.so.1 
  38. 002a9000-002aa000 rw-p 0001c000 08:07 4652       /lib/libgcc_s.so.1 
  39. 08048000-08049000 r-xp 00000000 08:08 264941     /home/peter/codes/testspace/testspace 
  40. 08049000-0804a000 r--p 00000000 08:08 264941     /home/peter/codes/testspace/testspace 
  41. 0804a000-0804b000 rw-p 00001000 08:08 264941     /home/peter/codes/testspace/testspace 
  42. 0804b000-0806c000 rw-p 00000000 00:00 0          [heap] 
  43. b7fe8000-b7fea000 rw-p 00000000 00:00 0  
  44. b7ffe000-b8000000 rw-p 00000000 00:00 0  
  45. bffeb000-c0000000 rw-p 00000000 00:00 0          [stack] 
  46.  
  47. Program received signal SIGABRT, Aborted. 
  48. 0x0012d422 in __kernel_vsyscall () 
  49. (gdb) bt 
  50. #0  0x0012d422 in __kernel_vsyscall () 
  51. #1  0x001714d1 in raise () from /lib/tls/i686/cmov/libc.so.6 
  52. #2  0x00174932 in abort () from /lib/tls/i686/cmov/libc.so.6 
  53. #3  0x001a7fc5 in ?? () from /lib/tls/i686/cmov/libc.so.6 
  54. #4  0x00228008 in __fortify_fail () from /lib/tls/i686/cmov/libc.so.6 
  55. #5  0x00227fc0 in __stack_chk_fail () from /lib/tls/i686/cmov/libc.so.6 
  56. #6  0x080484b2 in fn () at test.c:8 
  57. #7  0x00000000 in ?? () 

這裡便看到了:

  1. # #6  0x080484b2 in fn () at test.c:8  

以便我們鎖定問題。

很多時候,當內存溢出問題不嚴重時,並不會直接終止我們程序的運行。但是,我們會在調試程序中碰到非常奇怪的問題,比如某一個變量無緣無故變成亂碼,不管是在堆中,還是棧中。這便很有可能是指針的錯誤使用導致的。這種情況出現時,一種調試方法是:使用gdb加載程序,並用watch鎖定被改成亂碼的變量。這樣,如果這個變量被修改,程序便會停下來,我們就可以看到底是哪條語句修改了這個程序。

2. 內存洩漏

內存洩漏只會是在堆中申請的內存沒有釋放而導致的。也就是,我們在malloc()後沒有及時的進行free()。這裡,可以利用現有的一些軟件幫助我們調試,如Valgrind(http://valgrind.org)。使用方法請參見其主頁的幫助文檔。

3. 緩沖區:能大就大點

很多內存溢出的問題都是因為緩沖區不夠大。因此,我們在開辟緩沖區的時候,一定要給使用打出余量,不能每次想申請多少就申請多少,要想到這部分內存的用途,並進行上限估計。估不出來的時候盡量放大點。

當然,不能隨便的放大,可能會出現問題,比如:棧內申請空間過大,程序一使用變量直接段錯誤。

4. snprintf比sprintf好,那麼strncpy就比strcpy好?!

有經驗的前輩總是這樣說:”小同志,不要隨便用sprintf(),要用snprintf(),這樣如果打印的數據溢出了可以保護呀!“我們發現,這樣做雖然要多寫一個參數,但是的確比原來的程序安全了!何樂不為。

之後,我們又看到了strncpy(),一看就高興!又帶一個n!馬上用了一下:

  1. #include <stdio.h> 
  2. #include <string.h> 
  3.  
  4. void fn(void) 
  5.     char a[10]; 
  6.     strncpy(a, "hello", 100); 
  7.  
  8. int main(int argc, char *argv[]) 
  9.     fn(); 
  10.     return 0; 
  11. }

很好,程序崩了。

有心的人早就發現了,長度100明顯不對阿。可是有人也就想了,為啥10個字節還不夠放"hello"這些玩意呢?man一下才知道:

  1. STRCPY(3)                                              Linux Programmer's Manual                                              STRCPY(3) 
  2.  
  3. NAME 
  4.        strcpy, strncpy - copy a string 
  5.  
  6. SYNOPSIS 
  7.        #include <string.h> 
  8.  
  9.        char *strcpy(char *dest, const char *src); 
  10.  
  11.        char *strncpy(char *dest, const char *src, size_t n); 
  12.  
  13. DESCRIPTION 
  14.        The  strcpy() function copies the string pointed to by src, including the terminating null byte ('\0'), to the buffer pointed to 
  15.        by dest.  The strings may not overlap, and the destination string dest must be large enough to receive the copy. 
  16.  
  17.        The strncpy() function is similar, except that at most n bytes of src are copied.  Warning: If there is no null byte  among  the 
  18.        first n bytes of src, the string placed in dest will not be null terminated. 
  19.  
  20.        If the length of src is less than n, strncpy() pads the remainder of dest with null bytes. 
  21.  
  22.        A simple implementation of strncpy() might be: 
  23.  
  24.            char* 
  25.            strncpy(char *dest, const char *src, size_t n){ 
  26.                size_t i; 
  27.  
  28.                for (i = 0 ; i < n && src[i] != '\0' ; i++) 
  29.                    dest[i] = src[i]; 
  30.                for ( ; i < n ; i++) 
  31.                    dest[i] = '\0'; 
  32.  
  33.                return dest; 
  34.            } 
關鍵是最後的一句:
  1. "If the length of src is less than n, strncpy() pads the remainder of dest 
  2. with null bytes. " 

也就是說,strncpy並不僅僅是做一個n長度的保護,而會把剩下的字符清為0x00。要知道,snprintf()是沒這檔子事情的。所以,我們要記住:

snprintf()總是比sprintf()安全,但是strncpy()和strcpy()比就不一定了。 


總之,程序出問題是怎麼也避免不了的。特別是出現詭異的問題的時候,要學會冷靜分析產生問題的結果。往往這些問題都是我們編程過程中的錯誤導致的,而不是我們見鬼了。要對自己解決問題的能力有信心嘛!

程序這東西就是這樣,用好了,越用越順手;用不好,死都不知道怎麼死的。

本文出自 “LoudMouth Peter” 博客,請務必保留此出處http://xzpeter.blog.51cto.com/783279/329052

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved