未定义的行为如何未定义?

我不确定我是否理解未定义的行为会危害程序的程度。

假设我有这样的代码:

#include <stdio.h>

int main()
{
    int v = 0;
    scanf("%d", &v);
    if (v != 0)
    {
        int *p;
        *p = v;  // Oops
    }
    return v;
}

该程序的行为是否仅针对v为非零的情况而未定义,或者即使v为零也未定义?


我想说,只有当用户插入任何不同于0的数字时,行为才是未定义的。毕竟,如果违规代码段没有实际运行,UB的条件就不会被满足(即,未初始化的指针未被创建既没有解除引用)。

有一点可以在标准3.4.3中找到:

行为,在使用不可移植或错误的程序结构或错误数据时,本国际标准对此没有要求

这似乎意味着,如果这样的“错误数据”是正确的,那么行为将被完全定义 - 这似乎非常适用于我们的情况。


其他示例:整数溢出。 任何对用户提供的数据进行补充而没有对其进行广泛检查的程序都会受到这种未定义的行为的影响 - 但是只有当用户提供这些特定数据时,才会有UB的增加。


由于这里有语言律师的标签,我有一个非常挑剔的论点,即无论用户输入,程序的行为都是未定义的,但不是出于您可能期望的原因 - 虽然它可以很好地定义(当v==0 )取决于实施。

该程序将main定义为

int main()
{
    /* ... */
}

C99 5.1.2.2.1说主要功能应定义为

int main(void) { /* ... */ }

或as

int main(int argc, char *argv[]) { /* ... */ }

或同等学历; 或者以某种其他实现定义的方式。

int main()不等于int main(void) 。 前者作为一项声明说, main是采取一个固定但没有说明的数量和类型的论据; 后者说它没有任何争论。 不同之处在于递归调用main

main(42);

如果使用int main(void) ,则违反约束条件,但如果使用int main() ,则不会。

例如,这两个程序:

int main() {
    if (0) main(42); /* not a constraint violation */
}


int main(void) {
    if (0) main(42); /* constraint violation, requires a diagnostic */
}

并不等同。

如果实现文档接受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("Hellon");
        int *p;
        *p = v;  // Oops
    }
    return v;
}

如果你提供“1”作为输入,这个程序会做什么? 如果你回答是“它打印你好,然后崩溃”,你错了。 “未定义的行为”并不意味着某些特定语句的行为是未定义的; 这意味着整个程序的行为是不确定的。 允许编译器假定你不参与未定义的行为,所以在这种情况下,它可能会假定v不为零,并且完全不发送括号内的任何代码,包括printf

如果你认为这不太可能,那就再想一想。 GCC可能不会完全执行此分析,但它确实执行非常类似的分析。 我最喜欢的例子实际上说明了真实点:

int test(int x) { return x+1 > x; }

尝试编写一个小测试程序来打印出INT_MAXINT_MAX+1test(INT_MAX) 。 (确保启用优化。)典型实现可能会显示INT_MAX为2147483647, INT_MAX+1为-2147483648,并且test(INT_MAX)为1。

事实上,GCC编译这个函数返回一个常量1.为什么? 由于整数溢出是未定义的行为,因此编译器可能会认为你没有这样做,因此x不能等于INT_MAX ,因此x+1大于x ,因此此函数可无条件返回1。

未定义的行为可以并且确实会导致与自己不相等的变量,比较大于正数的负数(参见上面的示例)以及其他奇怪的行为。 编译器越聪明,行为越离奇。

好吧,我承认我不能引用标准的章节和诗句来回答你问的确切问题。 但是那些说“是的,但是在现实生活中,解引用NULL只是给出seg故障”的人比他们想象的更加错误,而且他们在每一代编译器中都会出错。

而在现实生活中,如果代码死了,你应该删除它; 如果它没有死掉,你不能调用未定义的行为。 所以这是我对你的问题的回答。

链接地址: http://www.djcxy.com/p/43877.html

上一篇: How undefined is undefined behavior?

下一篇: What is the simplest standard conform way to produce a Segfault in C?