理解这个示例程序

时间:2013-05-25 17:21:32

标签: c arrays pointers stack

请帮我理解C中的这个示例程序。

#include <stdio.h>
int i;
int *tmp;
void anotherFunction(void);
void destroyStack(int);
void main(void)
{
 anotherFunction();
 fprintf(stderr,
 "We will never reach this far\n");
}
void anotherFunction(void)
{
 destroyStack(4);
 fprintf(stderr,"In another function\n");
}
void destroyStack(int param)
{
 tmp = &param;
 for(i = -200; i < 10; i++)
 /* overwrite part of stack*/
 printf("%d\n",param), tmp[i] = 0;
}

AFAIK tmp是指向int的指针,它被视为一个数组,为什么会这样?作者试图通过这个名为“破坏堆栈”的例子来说明什么?什么时候使用像数组一样的指针是个好主意?这样编程是否合法?

5 个答案:

答案 0 :(得分:2)

tmp = &param;
tmp[i] = 0;

写入不属于的内存位置的结果。这会导致未定义的行为。这可能会破坏堆栈,如作者所说或完美地工作。它不是一个有效的C程序。

  

作者试图用这个名为“破坏堆栈”的例子来说明什么?

显然,他/她正试图证明摧毁堆栈。意图似乎是超出内存的范围,以便它可能破坏堆栈。然而,这是UB,它可能会或可能不会导致它。

答案 1 :(得分:1)

这里我们主要关注3件事

int i 的声明,指向int tmp 的指针,作为全局变量(不在堆栈中)

int i;
int *tmp;

4为参数

调用函数 destroyStack
destroyStack(4);

函数 destroyStack 本身

void destroyStack(int param) 
{
  tmp = &param;
  for(i = -200; i < 10; i++)
  /* overwrite part of stack*/
  printf("%d\n",param), tmp[i] = 0;
}

堆栈在其生命周期内方便且临时地保持(通常)相对较低的空间用于参数,局部变量和函数的返回地址。
有一个内部堆栈指针(位于CPU的寄存器中),它指示我们在堆栈中给定时间的位置 - 为该用途保留的内存空间。
当堆栈存储器被“借用”时,就像在函数调用期间一样,堆栈指针被增加(为简单起见 - 实际上在i386上它被递减),并且堆栈指针返回到函数返回时调用之前的位置。  这很方便,因为 1。不需要昂贵的动态分配(如via malloc 2。编译器在编译时知道是参数,局部变量和返回地址 - 它们都是相对于堆栈指针(+ x或-x)。

那么当调用 destroyStack

时会发生什么
  • 移动堆栈指针以为返回地址( destroyStack )和4参数(无局部变量)
  • 创建空间
  • 然后处理器“跳转”到 destroyStack
  • 的代码
  • tmp (global)获取 param 的地址(稍后显示其值)
  • 然后, param ,只是 int 的大小,被“淹没”210个 int 值(即210次)它能拥有什么...)。这是可能的,因为C只需要一个指针( tmp )来处理一系列值(如数组)。
  • 因此,堆栈空间从 int 的大小 - param 下面的 param - param <10> 包含 - 填充0。

在显示4之前,您应该多次看到 printf 显示0 param 被0覆盖)在 for 循环中 - 并且由于 destroyStack 的返回地址到 anotherFunction (在printf之前)很可能也会被覆盖, CPU将“跳转”到由零组成的地址 - 通常是保留区域,或者无论如何,进程无法访问/访问 - 并生成异常(崩溃)。

作者使用 i tmp 作为全局变量(非本地),以便它们不会被 destroyStack 覆盖在堆栈中,即破坏可以按计划进行!

答案 2 :(得分:0)

传递给函数的“param”将通过堆栈传递。它是如何工作的:C在调用函数之前存储一个返回地址(一些地址显示处理器在执行函数后要去哪里)和参数。通过&amp;获取“param”的地址我们只是得到一个堆栈的头。

在deployStack函数中,我们用零来回清除堆栈。我们可以这样做,为什么不呢。

但是当deployStack结束时,处理器会将地址排成一行以返回main。它不会得到它,因为我们只用0擦除它。

接下来发生的事情在很大程度上取决于处理器架构,甚至在C标准上也是如此。有一件事是肯定的 - 我们不会回到主要的。正如在那里直接说的那样。

答案 3 :(得分:0)

  

tmp是指向int的指针,它被视为一个数组,为什么会这样?

tmp实际上是单个元素整数数组。数组元素访问操作符可以被视为指针算术和解除引用的语法糖。因此,a[b]等同于*(a + b),并作为推论b[a]。除非访问无效的内存位置,否则这是合法且完全正常的:您不应访问未为变量分配的内存区域。

  

作者试图用这个名为“破坏堆栈”的例子来说明什么?

作者试图演示访问无效内存区域时可能发生的情况。从技术上讲,一切都可能发生。没有什么特别的。

  

何时使用像数组一样的指针是个好主意?这样编程是否合法?

是的,当然,这是一个非常常见的操作。您只需知道哪些数组索引有效(通常为0到size-1)。 (您可能需要将指针的大小传递给数组:struct my_arr { int *arr; size_t size; };。)


(我不认为作者想要证明较低内存区域中的访问冲突实际上不是问题,因为到目前为止堆栈上的空间没有被使用,但相比之下,访问冲突在除了变量param之外,更高的内存区域将覆盖,调用函数的返回地址并触发堆栈损坏保护机制(例如,金丝雀),从而触发分段错误。但对于任何感兴趣的人来说,{{{ 3}}。)

答案 4 :(得分:0)

  • AFAIK tmp是一个指向int的指针,它被视为一个数组, 为什么会这样?这样编程是否合法?

这是合法的。在C []中是执行添加和解除引用的运算符。因此

a[i]

等于

*(a + i)

你甚至可以写5[a]而不是a[5],因为*(a + 5)等于*(5 + a)。