首页 > 编程 > C++ > 正文

浅析C语言编程中的数组越界问题

2020-05-23 14:13:37
字体:大 中 小
来源:转载
供稿:网友

这篇文章主要介绍了浅析C语言编程中的数组越界问题,通过内存空间来讨论其导致的程序崩溃问题,需要的朋友可以参考下

因为C语言不检查数组越界,而数组又是我们经常用的数据结构之一,所以程序中经常会遇到数组越界的情况,并且后果轻者读写数据不对,重者程序crash。下面我们来分析一下数组越界的情况:

1) 堆中的数组越界

因为堆是我们自己分配的,如果越界,那么会把堆中其他空间的数据给写掉,或读取了其他空间的数据,这样就会导致其他变量的数据变得不对,如果是一个指针的话,那么有可能会引起crash

2) 栈中的数组越界

因为栈是向下增长的,在进入一个函数之前,会先把参数和下一步要执行的指令地址(通过call实现)压栈,在函数的入口会把ebp压栈,并把esp赋值给ebp,在函数返回的时候,将ebp值赋给esp,pop先前栈内的上级函数栈的基地址给ebp,恢复原栈基址,然后把调用函数之前的压入栈的指令地址pop出来(通过ret实现)。

栈是由高往低增长的,而数组的存储是由低位往高位存的 ,如果越界的话,会把当前函数的ebp和下一跳的指令地址覆盖掉,如果覆盖了当前函数的ebp,那么在恢复的时候esp就不能指向正确的地方,从而导致未可知的情况,如果下一跳的地址也被覆盖掉,那么肯定会导致crash。

-------------------------

压入的参数和函数指针

-------------------------

aa[4]

aa[3]

合法的数组空间 aa[2]

aa[1]

aa[0]

-------------------------

###sta.c###

 

 
  1. #include <stdio.h> 
  2.  
  3. void f(int ai) 
  4. { 
  5. int aa[5]={1,2,3}; 
  6. int i = 1; 
  7. for (i=0;i<10;i++) 
  8. aa[i]=i; 
  9. printf("f()/n"); 
  10. } 
  11.  
  12. void main() 
  13. { 
  14. f(3); 
  15. printf("ok/n"); 
  16. } 
  17.  
  18.  
  19.  
  20.  
  21.  
  22. ###sta.s### 
  23.  
  24. .file "sta.c" ;说明汇编的源程序 
  25. .section .rodata ;说明以下是只读数据区 
  26. .LC0: 
  27. .string "f()" ;"f()" 的类型是string,地址为LC0 
  28. .text ;代码段开始 
  29. .globl f ;f为全局可访问 
  30. .type f, @function ; f是函数 
  31. f: 
  32. pushl %ebp 
  33. movl %esp, %ebp 
  34. subl $40, %esp 
  35. movl $0, -24(%ebp) 
  36. movl $0, -20(%ebp) 
  37. movl $0, -16(%ebp) 
  38. movl $0, -12(%ebp) 
  39. movl $0, -8(%ebp) 
  40. movl $1, -24(%ebp) 
  41. movl $2, -20(%ebp) 
  42. movl $3, -16(%ebp) 
  43. movl $1, -4(%ebp) 
  44. movl $0, -4(%ebp) 
  45. jmp .L2 
  46. .L3: 
  47. movl -4(%ebp), %edx 
  48. movl -4(%ebp), %eax 
  49. movl %eax, -24(%ebp,%edx,4) 
  50. addl $1, -4(%ebp) 
  51. .L2: 
  52. cmpl $9, -4(%ebp) 
  53. jle .L3 
  54. movl $.LC0, (%esp) 
  55. call puts 
  56. leave 
  57. ret 
  58. .size f, .-f ;用以计算函数f的大小 
  59. .section .rodata 
  60. .LC1: 
  61. .string "ok" 
  62. .text 
  63. .globl main 
  64. .type main, @function 
  65. main: 
  66. leal 4(%esp), %ecx 
  67. andl $-16, %esp 
  68. pushl -4(%ecx) 
  69. pushl %ebp 
  70. movl %esp, %ebp 
  71. pushl %ecx 
  72. subl $4, %esp 
  73. movl $3, (%esp) 
  74. call f 
  75. movl $.LC1, (%esp) 
  76. call puts 
  77. addl $4, %esp 
  78. popl %ecx 
  79. popl %ebp 
  80. leal -4(%ecx), %esp 
  81. ret 
  82. .size main, .-main 
  83. .ident "GCC: (GNU) 4.1.2 20070115 (SUSE Linux)" ;说明是用什么工具编译的 
  84. .section .note.GNU-stack,"",@progbits 

从main函数开始压入f函数的参数开始,堆栈的调用情况如下

浅析C语言编程中的数组越界问题

图1 压入参数

浅析C语言编程中的数组越界问题

图二 通过call 命令压入下一跳地址 IP

浅析C语言编程中的数组越界问题

图三 函数f 通过pushl %ebp 把 ebp保存起来

浅析C语言编程中的数组越界问题

图四 函数 f 通过movl %esp, %ebp让ebp指向esp,这样esp就可以进行修改,在函数返回的时候用ebp的值对esp进行恢复

浅析C语言编程中的数组越界问题

图五 函数 f 通过subl $40, %esp 给函数的局部变量预留空间

浅析C语言编程中的数组越界问题

图六 int数组 aa[5]占用了20个字节的空间,然后 int i占用了4个字节的空间(紧邻着之前压入栈的%ebp)

故,如果aa[5]进行赋值,则会把 i 的值覆盖掉,

如果对aa[6]进行赋值,则会把 栈中的 %ebp 覆盖掉,那么在函数 f 返回的时候则不能对ebp进行恢复,即main函数的ebp变成了我们覆盖掉的值,程序不知道会发生什么事情,但因为我们的程序接下来没有调用栈中的内容,故还是可以运行的。

如果对aa[7]进行赋值,则会把栈中的 %IP 覆盖掉,在函数 f 返回的时候就不能正确地找到下一跳的地址,会crash;


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表