我在这里擦除堆栈吗?

时间:2014-09-05 08:10:04

标签: c linux gcc

我在Linux上使用gcc,下面的代码编译成功,但没有正确打印变量i的值,如果一次输入一个字符i跳转或减少到0。知道我在scanf上使用%d作为char(我试图擦除堆栈)。这是一个尝试擦除堆栈或其他东西的情况吗?(我想如果堆栈被删除,程序会崩溃)。

#include <stdio.h>

int main()
{
    int i;
    char c;

    for (i=0; i<5; i++) {
        scanf ("%d", &c);
        printf ("%d ", i);
    }
    return 0;
}

4 个答案:

答案 0 :(得分:3)

使用scanf(),您在int内插入char。一个char通常仅使用1个字节存储,因此您的编译器可能会溢出,但它可以覆盖或不覆盖其他值,具体取决于变量的对齐方式。

如果您的编译器为char保留1个字节,并且int的内存地址就在char的地址之后(可能就是这种情况),那么您的scanf()只会覆盖i的第一个字节。如果您在 little-endian 计算机中并输入小于256的值,则i将始终为0.

但如果输入更大的值,它会变大。尝试输入256; i将变为1.对于512,i将变为2,因此为1。

但是你不是“擦除堆栈”,只是覆盖了一些字节(实际上,你正在覆盖sizeof(int)个字节;其中一个对应于char而其他的可能都是int中的字节数,但只有一个。

如果你真的想要“擦除堆栈”,你可以这样做:

#include <string.h>

int
main(void) {
    char c;

    memset(&c, 0, 10000);

    return 0;
}

答案 1 :(得分:3)

main的参数外,堆栈上还有intchar。 让我们假设sizeof(int) == 4,只看一看ic

(              int i               )(char c )
[   0   ][   1   ][   2   ][ 3 (&i)][ 4 (&c)]

所以这实际上是没有argc*argv的堆叠布局。 在这种情况下,i消耗的内存是c的四倍。 堆栈以相反的方向增长,因此如果您写的内容大于charc,它将写入[4]并进一步向左,而不是向右。所以[5]永远不会被写入。而是覆盖[3]。 对于您将int写入cint的大小比c大四倍的情况,您实际上会写入[1][2][3][4],只需{{} 1}}不会被覆盖,但[0]的3/4内存将被破坏。

在大端系统上,int的最重要字节将存储在i中,因此会被此操作覆盖。在小端系统上,最重要的字节存储在[3]中并将被保留。尽管如此,你还是以这种方式破坏你的筹码。

正如艾姆斯提到的那样并非总是如此。可能存在不同的效率对齐,或者因为平台仅支持对齐访问,因此在变量之间留下间隙。此外,只要编译器没有as-if规则所述的可见副作用,就允许编译器进行任何优化。在这种情况下,变量可以完美地存储在寄存器中,并且永远不会保存在堆栈中。但是许多其他编译器优化和平台依赖性可能使这种方式变得更加复杂。 所以这只是最简单的情况,没有考虑平台依赖性和编译器优化,也似乎是在你的特殊情况下可能会发生一些细微差别。

答案 2 :(得分:3)

@foobar对一个架构上的编译器碰巧做了很好的描述。他(?)也给出了假设的大端编译器/系统可能的答案。

公平地说,这可能是最有用的答案,但它仍然不是正确的

没有规则说堆栈必须以某种方式增长(尽管,无论是左侧还是右侧驱动,必须做出选择,并且下降堆栈最常见的。)

没有规则说编译器必须以任何特定的方式在堆栈上布局变量。 C标准并不关心。甚至官方架构/特定于操作系统的ABI也不关心这一点,只要解开工作所需的位。在这种情况下,编译器甚至可以为系统中的每个函数选择不同的方案(而不是它可能)。

所有可以肯定的是,scanf尝试写一些int - 大小为char,并且这是未定义的。 在实践中有几种可能的结果:

  • 它工作正常,没有额外的东西被覆盖,你很幸运。 (也许intchar的大小相同,或者堆栈中有填充。)
  • 它会覆盖内存中char之后的数据,无论是什么。
  • 它会覆盖char之前和/或之后的数据。 (这可能发生在对齐的存储指令忽略写地址的底部位的架构上。)
  • 它因未对齐的访问异常而崩溃。
  • 它会检测堆栈涂鸦,打印邮件和reports the incident

当然,这一切都不会发生,因为您在启用-Wall -Wextra -Werror的情况下进行编译,而GCC会告诉您,您的scanf格式与您的变量类型不匹配。

答案 3 :(得分:2)

Kerrek SB 发表评论时,行为未定义

如您所知,您将char *传递给scanf函数,但告诉函数将其视为int *

可能(尽管不太可能)覆盖堆栈上的其他东西,例如i,前一个堆栈指针或返回地址。

可能只是覆盖未使用的字节,例如,如果编译器使用填充来对齐堆栈。

可能导致崩溃,例如,如果c的地址不是4或8字节对齐且平台要求取消引用int为4或者8字节对齐。

它可能会任何其他。

但答案仍然是 - 在这种情况下一切皆有可能。行为根本没有定义。