理解指针和局部范围

假设我有以下function:

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是LOCAL SCOPE。

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

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

为什么第一个printf(返回char *)打印随机值并受本地范围的影响,但不是第二个(返回int *)?

访问超出范围的方法的局部变量的两种方式都是Undefined Behavior。 这些是一些有效的方法:

 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 str[20] = "Hello world."; 

str是函数allocateMemory()本地函数,并且在退出函数后不再有效,因此如果未定义的行为,则将其访问范围。

 int x = 5; 

这同样适用于此。

您可以将数据放在堆上并返回指向它的指针是有效的。

 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; } 

将第一个函数更改为:

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

并看到差异。

现在说明:

当您返回本地数据的地址(变量或数组,无关紧要 – 它是AUTOMATIC变量)时,您可能会丢失数据或弄乱内存。 在第二次函数调用之后,整数数据是正确的,这是一个好运。 但是如果你返回STATIC变量的地址 – 没有错误。 您还可以从HEAP为数据和返回地址分配内存。

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

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

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

除了一些其他东西, allocateMemory的堆栈框架将包含字符串(char数组) str的20个元素。

对于这个function:

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

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

当函数返回时,标记堆栈顶部的堆栈指针一直下降到函数调用之前的位置。 但是,内存仍然存在于堆栈中,它不会被删除 – 这是一个非常简单且毫无意义的过程。 但是,不再有任何东西可以保护这个内存不被某些东西覆盖:它已被标记为“不需要”。

现在,你对printf的调用有什么区别? 好吧,当你调用printf ,它会获得自己的堆栈帧 。 它会覆盖前一个被调用函数的堆栈帧的剩余部分。

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

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

我听说有人不喜欢使用堆,以至于他们以下列方式使用这个“技巧”:它们在函数中形成一个堆栈数组并return一个指向它的指针,然后,在函数返回后,它们复制它在调用任何其他函数之前,将内容调用到调用者范围内的数组,然后继续使用它。 永远不要这样做,为了所有可能阅读您的代码的人。