理解指针和本地范围

Understanding pointers and local scope

假设我有以下功能:

char* allocateMemory() 
{
    char str[20] = "Hello world.";
    return str;
}

int* another()
{
    int x = 5;
    return &x;
}

int _tmain(int argc, _TCHAR* argv[])
{
    char* pString = allocateMemory();
    printf("%s\n", pString);

    int* blah = another();
    printf("%d %d \n", blah, *blah);

    return 0;
}

第一个 printf 打印随机值,因为 str 是局部范围。

第二个 printf 打印正确的值,blah = blah 的地址,*blah = 5

为什么局部作用域只影响处理数组的allocateMemory,而不影响整数?

为什么第一个 printf(返回 char* )打印随机值并受局部作用域影响,而第二个(返回 int* )不受影响?

访问超出范围的方法的局部变量的两种方式都是未定义行为。这些是一些有效的方法:

char* allocateMemory() 
{
    char* str= malloc(sizeof(char) * 20); //assuming C
    strcpy(str, "Hello World.");
    return str; //Valid 
}

const char* allocateMemory() 
{
    return "Hello world."; //Valid Hello World is in read only location
}

int* another()
{
    int *x = malloc(sizeof(int)); //assuming C
    *x = 5;
    return x; //Valid
}

将第一个函数改为:

char* allocateMemory() 
{
    static char str[20] = "Hello world.";
    return str;
}

看看区别。

现在解释:

当您 return 本地数据地址(变量或数组,无关紧要 - 它是 AUTOMATIC 变量)时,您有丢失数据或在内存中造成混乱的风险。幸运的是,在第二次函数调用后整数数据是正确的。但是如果你 return STATIC 变量的地址 - 没有错误。您也可以从 HEAP 为数据和 return 地址分配内存。

char str[20] = "Hello world.";

str 是函数 allocateMemory() 的局部变量,一旦退出该函数就不再有效,因此如果出现未定义的行为,将超出其范围访问它。

int x = 5;

这里也一样。

您可以将数据放在堆上并且return指向它的指针有效。

char *allocatememory()
{
   char *p = malloc(20); /* Now the memory allocated is on heap and it is accessible even after the exit of this function */
   return p; 
}

正如其他回答者所说,这两个当然都是UB。他们还提供了一些以适当方式做您想做的事情的好方法。但是您问的是为什么 这实际上发生在您的情况下。要理解它,您需要了解调用函数时堆栈中发生的情况。我将尝试提供一个 真正 的简化解释。

调用函数时,会在堆栈顶部创建一个新的堆栈帧。函数中的所有数据都放在栈帧中。所以,对于函数

char* allocateMemory() 
{
    char str[20] = "Hello world.";
    return str;
}

allocateMemory 的堆栈帧除了一些其他内容外,还将包含字符串(字符数组)的 20 个元素 str

对于这个函数:

int* another()
{
    int x = 5;
    return &x;
}

another 的堆栈帧将包含变量 x 的内容。

当函数 returns 时,标记堆栈顶部的 堆栈指针 会一直下降到函数调用之前的位置。但是,内存仍在堆栈中,不会被擦除——这是一个代价高昂且毫无意义的过程。但是,不再有任何东西可以保护此内存不被某些东西覆盖:它已被标记为 "unneeded".

现在,您对 printf 的调用有何不同?好吧,当您调用 printf 时,它会获得自己的 堆栈框架 。它会覆盖之前调用的函数堆栈帧的剩余部分。

第一种情况中,您只需将pString传递给printf。然后 printf 覆盖曾经是 allocateMemory 的堆栈帧的内存,并且曾经 str 的内存被东西覆盖 printf 需要使用字符串输出,比如迭代变量。然后它继续尝试获取你传递给它的指针指向的内存,pString...但是它刚刚覆盖了这个内存,所以它输出了你看起来像垃圾的东西。

第二种情况中,您首先获得了指针blah的值,它位于您的本地范围内。 然后 你用 *blah 取消引用它。现在有趣的部分来了:您已经完成取消引用 ,然后 您调用了另一个可以覆盖旧堆栈框架内容的函数。这意味着曾经是函数 another 中的变量 x 的内存仍然存在,并且通过取消引用指针 blah,您可以获得 x 的值。然后 然后 你将它传递给 printf,但是现在,printf 将覆盖 another 的堆栈框架并不重要:值你传递给它的现在有点像 "safe"。这就是为什么第二次调用 printf 会输出您期望的值。

我听说有些人非常不喜欢使用堆,所以他们按以下方式使用这个 "trick":他们在函数中形成一个堆栈数组,return 指向它的指针,然后,在函数 returns 之后,他们在调用任何其他函数之前 将其内容 复制到调用者作用域中的数组,然后继续使用它。 切勿这样做,为了所有可能阅读您代码的人。