理解指针和局部范围

时间:2015-02-02 06:34:12

标签: c++ c pointers

假设我有以下功能:

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 *)?

4 个答案:

答案 0 :(得分:5)

访问超出范围的方法的局部变量的两种方式都是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
}

答案 1 :(得分:1)

将第一个功能更改为:

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

并看到差异。

现在解释:

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

答案 2 :(得分:1)

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

答案 3 :(得分:1)

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

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

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

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

对于此功能:

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

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

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

现在,您拨打printf之间的区别是什么?好吧,当你致电printf时,它会获得自己的堆栈框架。它会覆盖前一个被调用函数的堆栈帧剩下的内容。

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

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

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