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

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;
}
3902 次点击
所在节点    C
38 条回复
234235
2017-08-18 16:20:28 +08:00
1. 第一段程序,返回的是一个指针指向的地址。编译器并不总能通过上下文判断你的返回值是否为非法地址。a 是局部变量,a 的地址应该是在堆栈中,不同的编译器和运行环境对堆栈的定义不同,可能就是在普通的内存段中,这时你返回了该地址,该地址是真实存在并且是可以被你这个程序访问的,那它当然能被正确打印出来。C 不负责对任何你用过的或申请到的内存的内容进行清除操作。

2.第二段程序错误,因为编译器识别到你返回的是一个局部变量的地址,这个是否报错同样受到编译器和环境以及你的设置的影响。有些可能就是 warning,有些可能就直接通过。说到底还是和编译器将局部变量放在哪里的关系更大。

另外,你说 'c 标准不可能让指针指向无效内存' ,这是完全的表达错误,应该是 C 规范要求你的程序中不能出现指向未知内存的操作,这样做是不可预料的,有可能造成程序崩溃,CPU 直接抛 Hard Fault。
如果你定义一个指针并随意的赋了一个地址值,编译器是不会报错的,这个值的正确与否是你来保证的。这也是 C 的灵活性的体现,如果 C 也对指针地址有过多的限制,很多程序就没法写了。
zhouheyang0919
2017-08-18 16:21:01 +08:00
@masteryi

C / C++ 是“不安全”的语言。用户代码可以访问任意地址,编译器对指针的有效性不做验证。

“ c 标准不可能让指针指向无效内存”
“ c/c++不让返回局部变量的指针和引用”
来源请求。
wwqgtxx
2017-08-18 16:26:44 +08:00
另外就是你的第一段代码如果用 GCC 的-O3 编译的话,编译器甚至都可以推测出来这里的值为 3
.file "test.cpp"
.text
.p2align 2,,3
.globl __Z1fv
.def __Z1fv; .scl 2; .type 32; .endef
__Z1fv:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
leal -4(%ebp), %eax
leave
ret
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "%d\12\0"
.text
.p2align 2,,3
.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 $4, %esp
call ___main
subl $8, %esp
pushl $3
pushl $LC0
call _printf
xorl %eax, %eax
movl -4(%ebp), %ecx
leave
leal -4(%ecx), %esp
ret
.def _printf; .scl 2; .type 32; .endef
wevsty
2017-08-18 16:36:10 +08:00
@masteryi
C 中从来没有规定指针必须指向有效的地址,事实上指针可以指向任何地址。为了最大的运行速度,C 标准中不存在运行时检查这种东西,编译时只检查语法错误和语法上就能发现的简单的逻辑错误。
这给了开发者最大限度的自由,也允许开发者使用一些不符合规范但是又确实有用的技巧。
比如:
const int i = 1;
int* p = (int*)&i;
*p = 2;
const 修饰过的变量规定上是只读不可修改的,但是通过这样的方法其实又是可以修改的。
编译器开发实现最大的依据当然是标准文档,但是基本上都不是 100%按照文档实现的。某种程度上确实是,想怎么实现都可以,并且相当多的行为没有被写进标准文档,这种时候开发编译器的开发者可以自己决定如何处理。
最后,给点建议,谦虚点没错的。
cloudyfsail
2017-08-18 16:36:46 +08:00
不应该做和能不能做是两回事,lz 应该是刚学习 c++吧,c++的复杂部分体现在这里,大量的未定义行为,至于你第二段代码有错误,上面有人说了,应该是编译器设置的问题,lz 要学习还很多啊,不要这么嚣张。
irexy
2017-08-18 16:49:40 +08:00
@wevsty
请教下结果怎么解释呢?

int main() {
const int a = 1;
int *p = (int*)&a;
cout << a << " " << *p << " " << p << " " << &a << endl;
*p = 2;
cout << a << " " << *p << " " << p << " " << &a << endl;

return 0;
}

1 1 0x7fff5151cf68 0x7fff5151cf68
1 2 0x7fff5151cf68 0x7fff5151cf68
wwqgtxx
2017-08-18 16:56:39 +08:00
@irexy 你应该看看生成的汇编,你在编译 cout a 的时候 a 就已经被直接替换成 1 了,所以并不会被改变,类似于#define 的效果
wevsty
2017-08-18 17:01:18 +08:00
@irexy
很好解释。
const 的大多数时候只是一个编译器用来判断的标志,编译器编译的时候在语法上拒绝对 const 修饰的变量直接进行修改。但是变量是在内存中存放的,const 并没有对这个内存空间施加写保护,所以通过指针就可以改掉 const 修饰的变量了。
wevsty
2017-08-18 17:04:58 +08:00
@wevsty 看错了,没注意结果。

和 @wwqgtxx 说的差不多,这个可能是编译器优化导致的行为。要看这个编译器生成的代码是怎么生成的,可能是编译器对这样简单类型的操作就直接给优化了。
suikator
2017-08-18 17:10:41 +08:00
大家散了吧 lz 已经跑路了
ivechan
2017-08-18 17:14:21 +08:00
一条准则:永远不要试图去解释未定义(undefined)的操作。
这种问题根本没有答案。
wevsty
2017-08-18 17:23:04 +08:00
@irexy 还有一点需要注意,C 和 C++是不太一样的。
C 中 const 强调的是只读不能修改,实际上但是实际上是变量。
C++中 const 强调的是这是一个常量,和#define 差不多,虽然最后也会有实际的内存空间,但是未必就会从内存里去读取这个值。因为在编译阶段就可以确定变量 a 的值了,所以可能编译器直接就编译成 mov eax,1 这样的东西了。
举个例子:
#include <stdio.h>
int main() {
const int a = 1;
int *p = (int*)&a;
printf("%d", a);
*p = 2;
printf("%d", a);
return 0;
}
你用.c 的后缀,使用 C 编译器得到的结果就是 12。但是如果使用.cpp 后缀用 C++编译器来编译,那么结果可能就是 11。
更进一步的如果加上 volatile 来修饰 a,那么用 C++编译器编译出来则又可能是 12。
magicO
2017-08-18 17:40:04 +08:00
这就是为啥至今不敢碰 C/Cpp 的原因。。。
carlonelong
2017-08-18 17:46:33 +08:00
为啥这么纠结 undefined behavior
wingkou
2017-08-18 18:23:14 +08:00
典型初学者对 undefined behavior 的迷惑
masteryi
2017-08-18 19:44:11 +08:00
楼主躲在角落不敢说话。。。原来第一段仅仅是编译通过了
gnaggnoyil
2017-08-19 14:45:12 +08:00
@cloudyfsail C 和 C++语言标准本身并不需要借助栈 /堆的概念来说明对象的生存期.你说的这两个玩意在 C 和 C++标准中都有自己专门的称呼的: automatic storage duration/allocated storage duration(C)或者 automatic storage duration/dynamic storage duration(C++)
@wevsty 无论是 C 还是 C++,试图通过 cast 来修改一个 const 左值的值都是未定义行为.const 在语义上就明确表示不可修改,为何非得要违反语义.
wevsty
2017-08-19 14:48:30 +08:00
@gnaggnoyil 举个例子而已,当然这种做法是不规范的也不应该提倡。

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

https://tanronggui.xyz/t/383939

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

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

© 2021 V2EX