为什么这段代码能正确执行?

2017-08-18 14:05:39 +08:00
 masteryi
/*不理解为啥这个正确*/
#include <stdio.h>
int * f();
int main(){
   int* result= f();
   printf("%d\n",*result);
   return 0;
}
int * f(){
    int a=3;
    int* i=&a;
    return i;
}

为什么上面这段代码能正确执行? a (左值)是不是一个内存地址里面存了 3 (右值)?那么上面这段代码 i 和 a 是不是指向了同一块内存,内存里的值是 3 ?然后函数结束是不是这块内存就被释放了 3 就没了?为啥还能通过?

/*这段代码错的可以理解*/
#include <stdio.h>
int * f();
int main(){
   int* result= f();
   printf("%d\n",*result);
   return 0;
}
int * f(){
    int a=3;
    return &a;
}
3723 次点击
所在节点    C
38 条回复
wevsty
2017-08-18 14:12:46 +08:00
内存空间不再使用的时候并不一定里面的内容就会马上改变。
这个时候内存空间里究竟是什么是不确定的,引用这个空间是未定义的行为。
masteryi
2017-08-18 14:20:16 +08:00
@wevsty 那为什么第二段代码出错
masteryi
2017-08-18 14:21:59 +08:00
@masteryi 发错你的意思是引用这个空间是未定义的那么第一段为什么能正确执行
tyrealgray
2017-08-18 14:27:34 +08:00
引用不能单独存在,返回类型也是错的
masteryi
2017-08-18 14:35:11 +08:00
@tyrealgray 那是一个取地址的运算符,不懂别来误导新手好吗?
wevsty
2017-08-18 14:36:02 +08:00
@masteryi
第二段代码我的 VS2015 上编译和运行都没问题(不算编译警告),并且运行结果和第一段代码一样。
未定义行为产生的结果本来就是不确定的,取决于你编译器的实现,没有固定的答案。
fcten
2017-08-18 14:36:06 +08:00
被释放不等于被覆盖,在没有向该地址写入新值之前,内存中的值不会改变。
suikator
2017-08-18 14:41:36 +08:00
C 允许您将指针指向已释放的内存块
tyrealgray
2017-08-18 15:06:41 +08:00
@masteryi 看错了😂,好久没写 C++了。
tyrealgray
2017-08-18 15:11:43 +08:00
再仔细看了一下,你如果设置个 timeout,你的 result 再打印出来的时候就不一定是那个值了,具体看编译器怎么决定的。感觉第一段和第二段都会是这种结果
misaka20038numbe
2017-08-18 15:11:56 +08:00
第二段为什么错了,返回 a 的地址,然后取值。
cgb1021
2017-08-18 15:28:24 +08:00
因为 f 要返回一个指向 int 的地址的变量(新的地址),return &a 只是返回了一个 int 的地址。就是说还缺了指向 int 的地址的地址。
zhouheyang0919
2017-08-18 15:41:47 +08:00
@masteryi

存取 stack pointer 以下的内存地址是未定义的行为。

“未定义”意味着所有对这块内存空间的操作可能给出正确的结果,但更可能导致不可预料的后果(错误的结果,Segfault,各种 corruption,或其他问题)。这取决于编译器内部代码生成和优化的实现。
cloudyfsail
2017-08-18 15:45:37 +08:00
a 存在与栈上,函数 f 执行完毕后虽然栈被回收了,但是返回的地址仍然是有效的,仍然可以访问,但是这是非法行为,c++里叫未定义行为,就是说可能执行起来没问题,也可能有问题,你这个例子里 result 指向的内存没那么快重新被分配使用,所以还是得到 3.另外其实两段代码是一样的。
masteryi
2017-08-18 15:58:02 +08:00
实现个蛋我都不想一一回复了,你们真的在你的机器上跑过吗? c 标准不可能让指针指向无效内存,你的编译器不是根据 c 标准写的想咋写就咋写想咋实现就咋实现吗? c/c++不让返回局部变量的指针和引用
hitmanx
2017-08-18 16:01:25 +08:00
两个都是非法的访问,但是具体会不会出现运行时的错误,这个是随机的,取决于当时内存管理的情况.虽然说因实现而异,但是在一般实现中,栈上的内容从高字节到低字节依次是
1) main 中在 f()之前定义的局部变量
2) f()的传参. 这儿为空,所以不占空间
3) 返回地址(PC)
4) f()中定义的局部变量

f()返回以后,栈指针就指向下一个空的,也就是 2)或者说 3)的位置.访问它以下的栈内存都是非法的.
wwqgtxx
2017-08-18 16:03:20 +08:00
看我这个代码运行后的结果就知道了
#include <stdio.h>
int * f();
int * f2();
int main(){
int* result= f();
f2();
printf("%d\n",*result);
return 0;
}
int * f(){
int a=3;
int* i=&a;
return i;
}
int * f2(){
int a=4;
int* i=&a;
return i;
}

-----------------------------------------
4

Process returned 0 (0x0) execution time : 0.188 s
Press any key to continue.
hitmanx
2017-08-18 16:06:51 +08:00
@masteryi 运行时错误和编译时错误是两码事,比如
int* p = (int*)0x100;
printf("%d", *p);
编译器认为这是完全合理的,只有运行才会出错
wwqgtxx
2017-08-18 16:08:09 +08:00
@masteryi “ c 标准不可能让指针指向无效内存”这句话本来就不成立,C 的指针完全可以指向一个不存在的内存,你直接写 int* i = (int*)500;这样一样能编译通过,只不过运行的时候一旦访问这个指针指向的内容,就不知道程序会不会炸掉了
“ c/c++不让返回局部变量的指针和引用”这句话也不对,应该说是不应该而不是不让,你上面自己的代码也证明了实际上是可以返回局部变量的指针和引用,并不会编译出错,但是到了运行期间一样死未定义行为
wwqgtxx
2017-08-18 16:19:45 +08:00
如果你想探根究底的话,就看看 GCC 生产的汇编
.file "test.cpp"
.text
.globl __Z1fv
.def __Z1fv; .scl 2; .type 32; .endef
__Z1fv:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl $3, -8(%ebp)
leal -8(%ebp), %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
leave
ret
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "%d\12\0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $20, %esp
call ___main
call __Z1fv
movl %eax, -8(%ebp)
movl -8(%ebp), %eax
movl (%eax), %eax
subl $8, %esp
pushl %eax
pushl $LC0
call _printf
addl $16, %esp
movl $0, %eax
movl -4(%ebp), %ecx
leave
leal -4(%ecx), %esp
ret
.def _printf; .scl 2; .type 32; .endef

看懂了这段汇编就能解释为什么函数结束了还能访问到那个 3 了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/383939

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX