我正在阅读一个帖子,其中有一个答案包含以下代码:
#define goto { int x = *(int *)0; } goto
作者在那里指出,每当一个人试图使用goto语句时,他的程序都会崩溃,那么问题来了:为什么?根据我的理解,int x = *(int *)0;
将内存地址中前4字节的任何内容分配给x
,但为什么这一定会使程序崩溃呢?
程序崩溃是因为您取消引用了一个NULL
指针,但是程序会崩溃这一点是未定义的。它可能会崩溃,也可能不会崩溃或者做些奇怪的事情。这是一种未定义行为,最好避免使用。
值得注意的是,goto
并非毫无用处。每个关键字都有其适用的场合,语言的作者和标准委员会成员仍有理由保留它(考虑到编程语言正在发生一些重大变革)。如果明智地使用,goto在C和C++中具有应有的位置。
仅仅为了让你明白这是未定义行为,我使用VC11在调试与发布模式下编译了上面的片段。在调试模式下,它崩溃了;而在发布模式下,编译器简单地优化掉了该语句,程序没有崩溃。
#define goto { int x = *(int volatile*)0; } goto
以获得更一致的崩溃。 - chqrlie0
转换为指针类型是一个空指针。它不一定是内存地址0。对空指针进行解引用是未定义的行为,编译器可以生成在执行此操作时具有任意行为的代码。 - Pascal Cuoq{ void *p, *q; p = 0; memset(&q, 0, sizeof q); return memcmp(&p, &q, sizeof q); }
并不一定会返回 0
。实际上我不知道有哪台计算机不会返回 0
,但C标准就是这么说的。 - chqrlie该代码有两个未定义行为。
首先,重新定义语言关键字本身就会产生未定义行为。
其次,正如其他人所说,值为`0'的指针是NULL指针,对其进行解引用将产生未定义行为。
因此,这种构造的实际结果是未定义的。未定义行为的常见症状是程序崩溃。然而,崩溃并不是真正保证的。甚至没有必要。
至于为什么编写宏,那段代码的作者显然是“goto是邪恶的”阵营的拥护者,并认为任意地强加他/她的无知偏见于他人是合适的。事实上,即使有更好的替代方法,goto
也有其用途。即使Dijkstra(通常被引用来证明不使用goto
)只描述了无节制使用的问题,也没有声称它应该被简单地避免。Donald Knuth在70年代中期撰写了一篇论文,其中包括使用 goto
有益的例子。
goto
,那么不可靠的运行时错误是一个愚蠢的方式。更好的方法是:#define goto @
将强制在任何使用goto
的地方产生语法错误。更好的方法是,在你的编码标准中禁止使用goto
;如果你不能信任你的程序员遵循你的编码标准,那么你就有更大的问题了。 - Keith Thompson