程序崩了,咋辦?

這兩天由於調程序,本身簡單的總結了一下C編程中碰到的內存有關的問題和注意事項。html


1. 內存溢出是啥?

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

  
  
  
  
  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...":redis

  
  
  
  
  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命令看:sql

  
  
  
  
  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  

以便咱們鎖定問題。ubuntu

不少時候,當內存溢出問題不嚴重時,並不會直接終止咱們程序的運行。可是,咱們會在調試程序中碰到很是奇怪的問題,好比某一個變量平白無故變成亂碼,不論是在堆中,仍是棧中。這便頗有多是指針的錯誤使用致使的。這種狀況出現時,一種調試方法是:使用gdb加載程序,並用watch鎖定被改爲亂碼的變量。這樣,若是這個變量被修改,程序便會停下來,咱們就能夠看究竟是哪條語句修改了這個程序。數組

2. 內存泄漏

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

3. 緩衝區:能大就大點

不少內存溢出的問題都是由於緩衝區不夠大。所以,咱們在開闢緩衝區的時候,必定要給使用打出餘量,不能每次想申請多少就申請多少,要想到這部份內存的用途,並進行上限估計。估不出來的時候儘可能放大點。less

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

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()比就不必定了。 


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

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

相關文章
相關標籤/搜索