引用超出范围的char*

Mat*_*000 35 c

我最近在用C++编程了一段时间之后再次开始用C编程,而我对指针的理解有点生疏.

我想问一下为什么这段代码没有导致任何错误:

char* a = NULL;
{
    char* b = "stackoverflow";
    a = b;
}

puts(a);
Run Code Online (Sandbox Code Playgroud)

我认为因为b超出范围,a应该引用一个不存在的内存位置,因此它们在调用时会出现运行时错误printf.

我在MSVC中运行此代码大约20次,并且没有显示错误.

dbu*_*ush 45

b定义的范围内,为其分配字符串文字的地址.这些文字通常位于内存的只读部分,而不是堆栈.

当你做a=b你指定ba,即a现在包含一个字符串的地址.b超出范围后,此地址仍然有效.

如果你采取了地址b,然后试图取消引用该地址,那么你就调用未定义行为.

所以,你的代码是有效的,并没有发生未定义行为,但下列情况:

int *a = NULL;
{
    int b = 6;
    a = &b;
}

printf("b=%d\n", *a);
Run Code Online (Sandbox Code Playgroud)

另一个更微妙的例子:

char *a = NULL;
{
    char b[] = "stackoverflow";
    a = b;
}

printf(a);
Run Code Online (Sandbox Code Playgroud)

此示例与您的示例之间的区别在于b,它是一个数组,在指定时会衰减到指向第一个元素的指针a.所以在这种情况下a包含一个局部变量的地址,然后超出范围.

编辑:

作为旁注,将变量作为第一个参数传递是不好的做法printf,因为这会导致格式字符串漏洞.最好使用字符串常量如下:

printf("%s", a);
Run Code Online (Sandbox Code Playgroud)

或者更简单:

puts(a);
Run Code Online (Sandbox Code Playgroud)

  • 我认为即使后一个代码也可能优化为`printf("stackoverflow")`取决于你正在使用的编译器/开关 (3认同)
  • @CometEngine很高兴我能提供帮助.如果您发现它有用,请随意[接受此答案](https://stackoverflow.com/help/accepted-answer). (2认同)
  • @Govind Parmar刚刚在msvc上进行了全面优化和内联测试; 似乎是这样的. (2认同)

Sig*_*iSv 11

逐行,这是您的代码所做的:

char* a = NULL;
Run Code Online (Sandbox Code Playgroud)

a是一个不引用任何东西的指针(设置为NULL).

{
    char* b = "stackoverflow";
Run Code Online (Sandbox Code Playgroud)

b是一个引用静态常量字符串文字的指针"stackoverflow".

    a = b;
Run Code Online (Sandbox Code Playgroud)

a设置为也引用静态,常量字符串文字"stackoverflow".

}
Run Code Online (Sandbox Code Playgroud)

b超出范围.但是,由于a没有引用b,那么这也不要紧(它只是引用相同的静态常量字符串文字作为b被引用).

printf(a);
Run Code Online (Sandbox Code Playgroud)

打印"stackoverflow"引用的静态常量字符串文字a.

  • 空指针不"指向NULL".它*是*NULL. (3认同)

kyl*_*yle 11

字符串文字是静态分配的,因此指针无限期有效.如果您已经说过char b[] = "stackoverflow",那么您将在堆栈上分配一个char数组,当该范围结束时该数组将变为无效.这种差异也表现为修改字符串:char s[] = "foo"stack分配一个你可以修改的字符串,而char *s = "foo"只给你一个指向可以放在只读内存中的字符串的指针,所以修改它是未定义的行为.


zwo*_*wol 9

其他人已经解释说这段代码完全有效.这个答案是关于你的期望,如果代码无效,调用时会出现运行时错误printf.不一定如此.

让我们看一下代码中的这种变化,这无效的:

#include <stdio.h>
int main(void)
{
    int *a;
    {
        int b = 42;
        a = &b;
    }
    printf("%d\n", *a); // undefined behavior
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这个程序有不确定的行为,但它恰好是相当可能的,这将在事实上,印刷品42几个不同的原因-许多编译器将离开堆栈槽b分配的整个身体main,因为没有别的需要的空间和最小化堆栈调整的数量简化了代码生成; 即使编译没有正式解除分配栈槽,数字42很可能保留在内存中,直到别的东西覆盖它,并没有什么之间a = &b,并*a做到这一点; 标准优化("常量和复制传播")可以消除这两个变量,并将最后已知的值*a直接写入printf语句中(就像您已经编写过一样printf("%d\n", 42)).

理解"未定义的行为"并不意味着"程序会以可预测的方式崩溃",这一点至关重要.这意味着"任何事情都可能发生",而且任何事情都包括看起来像程序员可能想要的那样(在台计算机上,今天用这个编译器).


作为最后一点,我没有方便访问(Valgrind,ASan,UBSan)的激进调试工具,没有足够详细的跟踪"自动"变量生命周期来捕获此错误,但是GCC 6确实产生了这个有趣的警告:

$ gcc -std=c11 -O2 -W -Wall -pedantic test.c
test.c: In function ‘main’:
test.c:9:5: warning: ‘b’ is used uninitialized in this function
    printf("%d\n", *a); // undefined behavior
    ^~~~~~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

我相信这里发生的事情是,它没有我上述优化-复制的最后已知值b*a再进printf-但它的"最后已知值"的b是"这个变量是未初始化的"定点,而不是42(这然后生成相当于printf("%d\n", 0).)的代码.)