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


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