我们知道自动变量在函数返回时被销毁。
那么,为什么这个C程序返回正确的值?
#include <stdio.h>
#include <process.h>
int * ReturningPointer()
{
int myInteger = 99;
int * ptrToMyInteger = &myInteger;
return ptrToMyInteger;
}
main()
{
int * pointerToInteger = ReturningPointer();
printf("*pointerToInteger = %d\n", *pointerToInteger);
system("PAUSE");
}
输出
*pointerToInteger = 99
那为什么这会给出垃圾值?
#include <stdio.h>
#include <process.h>
char * ReturningPointer()
{
char array[13] = "Hello World!";
return array;
}
main()
{
printf("%s\n", ReturningPointer());
system("PAUSE");
}
xŤ
答案 0 :(得分:4)
该问题没有答案:您的代码表现出未定义的行为。它可以打印出“正确的价值”,如你所见,它可以打印任何其他东西,它可能是段错误,它可以用你的信用卡在线订购披萨。
取消引用main
中的指针是非法的,它不会指向该点的有效内存。不要这样做。
两个示例之间存在很大差异:在第一种情况下,*pointer
在调用printf
之前进行评估。因此,假设在获得指针值的行与printf
之间没有函数调用,则堆栈位置pointer
指向的位置可能不会被覆盖。因此,可能会输出在调用printf
之前存储的值(值将传递到printf
的堆栈,而不是指针)。< / p>
在第二种情况下,您将指向堆栈的指针传递给printf
。对printf
的调用会覆盖指针所指向的相同堆栈区域(的一部分),而printf
最终会尝试打印自己的堆栈(或多或少),这些堆栈没有很高包含可读内容的机会。
请注意你不能依赖于乱搞。只要符合标准规定的要求,您的实现就可以免费为printf
调用使用不同的堆栈。
答案 1 :(得分:2)
这是未定义的行为,它本可以发射导弹。但它恰好给你正确答案。
想一想,它有道理 - 你还期待什么?它应该给你零吗?如果是这样,那么编译器必须在作用域末端插入特殊指令以擦除变量的内容 - 浪费资源。编译器最自然的做法是保持内容不变 - 所以你只是偶然得到了未定义行为的正确输出。
您可以说此行为是实现定义的。例如。另一个编译器(或“发布”模式下的相同编译器)可能决定纯粹在寄存器中分配myInteger
(不确定它是否实际上可以在获取它的地址时执行此操作,但是为了参数... 。),在这种情况下,不会为99
分配任何内存,你会得到垃圾输出。
作为一个更具说明性(但完全未经测试)的示例 - 如果您插入一些malloc
并在printf
之前运用一些内存使用量,您可能会发现您正在寻找的垃圾值:P
您需要的“真实”答案需要在反汇编中得到解答。一个好的起点是gcc -S
和gcc -O3 -S
。我将对将要出现的向导进行深入分析。但是我使用GCC粗略地看了一眼,结果printf("%s\n")
被转换为puts
,因此调用约定是不同的。由于局部变量是在堆栈上分配的,因此调用函数可以“破坏”先前分配的局部变量。
答案 2 :(得分:1)
毁灭是错误的单词imho。本地驻留在堆栈上,如果函数返回堆栈空间,则可以再次重用。在此之前它不会被覆盖,仍然可以通过你可能不想要的指针访问(因为这可能永远不会指向有效的东西)
指针用于寻址内存中的空间,因为本地指针与我在1中描述的相同是有效的。但是指针似乎传递给主程序。
如果它确实是存储前一个整数的地址,那么当程序覆盖此内存位置时,在执行程序时直到“99”为止。它也可能是巧合的另一个99。任何方式:不要这样做。
这些错误有一天会导致麻烦,可能会出现在其他机器,其他操作系统,其他编译器或编译器选项上 - 想象一下您升级编译器可能会改变内存使用行为甚至构建优化标志,例如发布版本与默认调试版本,您可以命名。
答案 3 :(得分:0)
在大多数C / C ++程序中,它们的局部变量存在于堆栈中,destroyed
表示overwritten
表示其他内容。在这种情况下,当特定位置作为参数传递给printf()
时,该位置尚未被覆盖。
当然,拥有这样的代码会遇到麻烦,因为根据C和C ++标准,它会表现出不确定的行为。
答案 4 :(得分:0)
那是undefined behavior
。这意味着任何事情都可能发生,甚至是你所期望的。
UB的棘手部分在于它能为您提供您期望的结果,因此您认为自己正在做得正确。然后,程序中不相关部分的任何变化都会改变......
更具体地回答您的问题,您正在返回一个指向自动变量的指针,该函数在函数返回时不再存在,但由于您在中间不调用其他函数,因此它会保留旧值。
如果你打电话,例如printf
两次,那么第二次它很可能打印出不同的值。
答案 5 :(得分:0)
关键思想是变量表示存储在内存中的值的名称和类型。当它被“销毁”时,意味着a)不能再使用该名称访问该值,并且b)可以自由覆盖内存位置。
行为未定义,因为编译器的实现可以自由选择“破坏”后实际覆盖位置的时间。