【问题标题】:Static global variables vs global variables C静态全局变量与全局变量 C
【发布时间】:2015-06-12 15:38:21
【问题描述】:

我有下面的程序。如果我声明变量 a、b、c 静态全局变量,它会给出分段错误,但如果我将它们声明为非静态全局变量或局部变量,它不会给出分段错误。为什么它会以这种方式表现?我知道变量可以存储的数据多于变量可以存储的数据,但是为什么只有声明为静态时它会给出段错误?静态声明的变量是否存储在不允许覆盖的堆栈帧的某些不同部分?

编辑:我知道 strcpy 不安全。但这不是我的问题。我想了解为什么一个溢出会产生段错误,为什么另一个溢出可能不会产生段错误。

#include<stdio.h>
#include<string.h>

static char a[16];
static char b[16];
static char c[32];

int main(int argc, char *argv[]){

// char a[16];
 //char b[16];
 //char c[32];
    strcpy(a,"0123456789abcdef");
    strcpy(b,"0123456789abcdef");
    strcpy(c,a);
    strcpy(c,b);
    printf("a = %s\n",a);
    return 0;
}

【问题讨论】:

  • 当您写入超出缓冲区的末尾时,您正在写入任意内存,这可能具有未定义的行为。
  • 这就是所谓的未定义行为。不要询问未定义的内容。
  • 如果它们是静态的,您只会遇到崩溃,这很奇怪,因为静态和非静态全局变量之间的区别应该只是范围问题。 (当然,这根据C语言允许的行为,它说任何事情都可能发生)
  • 我怀疑您的编译器确实以与非静态全局变量不同的方式分配静态全局变量,因此在 c 结束后不久恰好有一个 0 字节,但没有时间它们不是静态的。
  • 访问超出缓冲区末尾的内存是未定义的行为。由于它是未定义的行为,任何事情都可能发生。任何事物。因此,有时该行为是段错误事件。有时它是隐藏的东西会破坏其他数据,如堆栈调用链。有时它是良性的。永远不要期望某种行为。任何时候都应避免这种未定义的行为。当某些数据损坏时,代价可能是巨大的,如果应用程序以段错误事件结束,用户会非常不高兴。

标签: c stack-overflow stack-frame


【解决方案1】:

注意 C 中的 const char* 字符串始终以 0 结尾,这意味着字符串“0123456789abcdef”实际上是 17 个字符:"0123456789abcdef\0"

我建议您始终使用安全版本

strncpy() 

您还可以查看明确告诉您包含空字符的文档。

http://www.cplusplus.com/reference/cstring/strcpy/

【讨论】:

  • strncpy 不安全,在这种情况下也无济于事。在极少数情况下strncpy 是正确使用的函数
  • @slux83 谢谢。我知道,但我的问题是为什么它不是静态时不给出段错误。
  • 它不会给你一个未定义的行为。如果您的程序不会那么短,它也可能会出现段错误。堆栈损坏的问题是您发现崩溃的时间是不可预测的。
  • @Matt 使用 strncpy 始终是一个好习惯,因为您限制了指定缓冲区大小的副本,以防您因为某些不明原因错过空字符
  • @slux83 不,这是一种不好的做法。在这种情况下使用strncpy 仍然会导致缓冲区溢出(尽管排序不同),因为 strncpy 不会在字符串不适合时以空值终止。
【解决方案2】:

内存对齐在堆栈变量中很重要。 尝试使用 -fstack-protector-strong 或类似的堆栈保护选项,您将看到崩溃。 还要在 c 之后声明一个 int 并溢出你的数组 c,你可以看到崩溃。 您需要确保没有填充。 因为 b 是一个数组,所以从 'a' 溢出的任何内容都会转到 b。 尝试类似:

struct foo {
        char c[10];
        int x;
    } __attribute__((packed));

你会在溢出 c 时看到崩溃。

当你溢出时你会遇到未定义的行为。

【讨论】:

  • 这里没有任何堆栈变量。
  • 只创建一个 struct foo f;