请帮我理解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 = ¶m;
for(i = -200; i < 10; i++)
/* overwrite part of stack*/
printf("%d\n",param), tmp[i] = 0;
}
AFAIK tmp
是指向int的指针,它被视为一个数组,为什么会这样?作者试图通过这个名为“破坏堆栈”的例子来说明什么?什么时候使用像数组一样的指针是个好主意?这样编程是否合法?
答案 0 :(得分:2)
tmp = ¶m;
tmp[i] = 0;
写入不属于的内存位置的结果。这会导致未定义的行为。这可能会破坏堆栈,如作者所说或完美地工作。它不是一个有效的C程序。
作者试图用这个名为“破坏堆栈”的例子来说明什么?
显然,他/她正试图证明摧毁堆栈。意图似乎是超出内存的范围,以便它可能破坏堆栈。然而,这是UB,它可能会或可能不会导致它。
答案 1 :(得分:1)
这里我们主要关注3件事
int i 的声明,指向int tmp 的指针,作为全局变量(不在堆栈中)
int i;
int *tmp;
以4
为参数
destroyStack(4);
函数 destroyStack 本身
void destroyStack(int param)
{
tmp = ¶m;
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
时会发生什么4
参数(无局部变量)在显示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)
这是合法的。在C []
中是执行添加和解除引用的运算符。因此
a[i]
等于
*(a + i)
你甚至可以写5[a]
而不是a[5]
,因为*(a + 5)等于*(5 + a)。