理解指针和本地范围
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 之后,他们在调用任何其他函数之前 将其内容 复制到调用者作用域中的数组,然后继续使用它。 切勿这样做,为了所有可能阅读您代码的人。
假设我有以下功能:
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 之后,他们在调用任何其他函数之前 将其内容 复制到调用者作用域中的数组,然后继续使用它。 切勿这样做,为了所有可能阅读您代码的人。