Meh*_*dad 34 c undefined-behavior language-lawyer
我不确定我是否完全理解未定义行为可能危及程序的程度.
假设我有这段代码:
#include <stdio.h>
int main()
{
int v = 0;
scanf("%d", &v);
if (v != 0)
{
int *p;
*p = v; // Oops
}
return v;
}
Run Code Online (Sandbox Code Playgroud)
这个程序的行为是否仅针对v非零的情况而定义,或者即使v为零也未定义?
Mat*_*lia 15
我会说只有当用户插入任何不同于0的数字时,行为才是未定义的.毕竟,如果实际上没有运行违规代码部分,则不满足UB的条件(即未创建未初始化的指针)既没有被解除引用).
在3.4.3标准中可以找到这方面的提示:
使用不可移植或错误的程序结构或错误数据时的行为,本国际标准不对此要求
这似乎意味着,如果这样的"错误数据"是正确的,那么行为将被完美地定义 - 这似乎非常适用于我们的情况.
附加示例:整数溢出.任何使用用户提供的数据进行添加而不对其进行大量检查的程序都会受到这种未定义的行为的影响 - 但只有当用户提供此类特定数据时才添加UB.
Kei*_*son 12
由于这有一个语言 - 律师标签,我有一个非常挑剔的论点,即程序的行为是不确定的,无论用户输入,但不是出于你可能期望的原因 - 尽管它可以很好地定义(何时v==0)取决于实现.
该计划定义main为
int main()
{
/* ... */
}
Run Code Online (Sandbox Code Playgroud)
C99 5.1.2.2.1表示主要功能应定义为
int main(void) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)
或者作为
int main(int argc, char *argv[]) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)
或同等学历; 或者以某种其他实现定义的方式.
int main()不等于int main(void).前者作为一种宣言,表示main采用固定但未指明的数量和类型的论点; 后者表示不需要任何论据.区别在于递归调用main等
main(42);
Run Code Online (Sandbox Code Playgroud)
如果您使用int main(void),则违反约束,但如果您使用则不会int main().
例如,这两个程序:
int main() {
if (0) main(42); /* not a constraint violation */
}
Run Code Online (Sandbox Code Playgroud)
int main(void) {
if (0) main(42); /* constraint violation, requires a diagnostic */
}
Run Code Online (Sandbox Code Playgroud)
不等同.
如果它接受的实现文档int main()作为扩展,那么这不适用于该实现.
这是一个极其挑剔的观点(不是每个人都同意这一点),并且通过声明int main(void)(无论如何你应该做什么)很容易避免;所有函数都应该有原型,而不是旧式声明/定义.
在实践中,我见过的每个编译器都会int main()毫无怨言地接受.
要回答有意的问题:
一旦进行了这种改变,如果是v==0,并且未定义,则程序的行为是明确定义的v!=0.是的,程序行为的定义取决于用户输入.这没什么特别不寻常的.
让我举一个论据,为什么我认为这仍然是未定义的.
首先,响应者说这是"大部分定义"或某些,基于他们对某些编译器的经验,是错误的.您的示例的一个小修改将用于说明:
#include <stdio.h>
int
main()
{
int v;
scanf("%d", &v);
if (v != 0)
{
printf("Hello\n");
int *p;
*p = v; // Oops
}
return v;
}
Run Code Online (Sandbox Code Playgroud)
如果您提供"1"作为输入,该程序会执行什么操作?如果你的答案是"它打印你好,然后崩溃",你错了."未定义的行为"并不意味着某些特定语句的行为未定义; 这意味着整个程序的行为是不确定的.允许编译器假设您不参与未定义的行为,因此在这种情况下,它可能假设v非零并且根本不发出任何括号内的代码,包括printf.
如果您认为这不太可能,请再想一想.GCC可能不会完全执行此分析,但它确实执行非常类似的分析.我最喜欢的例子实际上说明了真实的观点:
int test(int x) { return x+1 > x; }
Run Code Online (Sandbox Code Playgroud)
尝试写一个小测试程序打印出来INT_MAX,INT_MAX+1和test(INT_MAX).(确保启用优化.)典型的实现可能显示INT_MAX为2147483647,INT_MAX+1为-2147483648,test(INT_MAX)为1.
实际上,GCC编译此函数以返回常量1.为什么?因为整数溢出是未定义的行为,因此编译器可能假设你没有这样做,因此x不能相等INT_MAX,因此x+1大于x,因此这个函数可以无条件地返回1.
未定义的行为可以而且确实会导致与自身不相等的变量,比较大于正数的负数(参见上面的示例)以及其他奇怪的行为.编译器越聪明,行为越离奇.
好的,我承认我不能引用标准的章节和诗句来回答你问的确切问题.但是那些说"是的,但在现实生活中取消引用NULL只会产生一个seg错误"的人比他们想象的更加错误,并且他们在每一代编译器中都会出错.
在现实生活中,如果代码已经死了,你应该删除它; 如果它没有死,你不能调用未定义的行为.这就是我对你问题的回答.