这是未定义的C行为吗?
这段代码产生未定义行为的原因是,表达式++x
和x + 1
修改了x
,并且读取了x
,而这两个操作之间没有序列点。这在C(和C++)中导致了未定义行为。这个要求在C语言标准的6.5/2中给出。
解决这个问题的方法是在修改x
和读取x
之间添加一个序列点。这可以通过将这两个操作分开或者使用一个中间变量来实现。
需要注意的是,这个问题的未定义行为与printf
函数只给出了一个格式说明符和两个实际参数的事实无关。在C中,给printf
提供比格式字符串中的格式说明符更多的参数是完全合法的。问题的根源在于违反了C语言的表达式评估要求。
另外需要注意的是,一些讨论中的参与者没有理解未定义行为的概念,并坚持将其与未指定行为的概念混淆。为了更好地说明区别,让我们考虑以下简单的例子:
int inc_x(int *x) { return ++*x; } int x_plus_1(int x) { return x + 1; } int x = 1; printf("%d", inc_x(&x), x_plus_1(x));
上面的代码与原始代码“等效”,只是将涉及到x
的操作包装到函数中。在这个最新的例子中会发生什么呢?这段代码没有未定义行为。但是由于printf
参数的评估顺序是未指定的,这段代码会产生未指定行为,即printf
可能会被调用为printf("%d", 2, 2)
或printf("%d", 2, 3)
。在这两种情况下,输出都将是2
。然而,这个变体的重要区别在于,对x
的所有访问都被包装在每个函数开头和结尾的序列点中,因此这个变体不会产生未定义行为。
这正是一些其他帖子中的人试图强加给原始例子的推理。但是不能这样做。原始例子会产生未定义行为,这是完全不同的问题。显然,他们显然试图坚持认为在实践中,未定义行为总是等同于未指定行为。这是一个完全错误的说法,只能表明说这种说法缺乏专业知识的人的水平。原始代码产生未定义行为,就是这样。
继续这个例子,让我们修改之前的代码示例:
printf("%d %d", inc_x(&x), x_plus_1(x));
代码的输出将变得不可预测。它可能打印2 2
,也可能打印2 3
。然而需要注意的是,即使行为是不可预测的,它仍然不会产生未定义行为。行为是未指定的,但不是未定义的。未指定行为只有两种可能性:要么是2 2
,要么是2 3
。未定义行为没有任何限制。它可以格式化你的硬盘而不是打印一些东西。感受到区别了吗。
原文链接:https://stackoverflow.com/questions/949433
这段内容讨论了在C语言中,程序行为未定义时可能会发生的情况。讨论了函数参数的概念性评估是并行进行的,也就是说在它们的评估之间没有序列点。这意味着表达式`++x`和`x+1`可以按照这个顺序、相反的顺序或者以一种交错的方式进行评估。当你同时修改一个变量并尝试访问它的值时,行为是未定义的。
在许多实现中,参数是按顺序进行评估的(虽然不总是从左到右)。因此,在现实世界中,你可能只会看到2。
然而,编译器可以生成这样的代码:
1. 将`x`加载到寄存器`r1`中。
2. 通过将1加到`r1`中来计算`x+1`。
3. 通过将1加到`r1`中来计算`++x`。这是可以的,因为`x`已经加载到了`r1`中。根据编译器的设计,第2步不能修改`r1`,因为这只会在两个序列点之间发生,而C标准禁止这样做。
4. 将`r1`存储到`x`中。
在这个(假设的,但正确的)编译器上,程序将打印3。
文章还讨论了一些其他问题,比如`printf`函数中的格式参数个数应该与实际参数个数匹配,否则行为是未定义的。还讨论了可能导致正确编译器失败的情况。
C语言中行为未定义的代码会导致程序产生任意的结果。为了避免这种情况,需要编写符合规则的代码。
解决这个问题的方法是遵循C语言的规范,并确保代码中没有行为未定义的情况。
这段内容讨论了一个关于C语言行为的问题。问题是,当使用printf函数打印两个参数时,第一个参数是一个表达式`++x`,第二个参数是另一个表达式`x+1`。根据C语言标准,这个代码片段可能会引起未定义行为。
C语言标准规定,在两个序列点之间,对象的存储值最多只能被一个表达式的求值修改,并且先前的值只能被读取来确定要存储的值。在调用函数之前,对函数参数的求值之前有一个序列点,而在所有参数求值之后(但函数尚未调用)有一个序列点。在这两个序列点之间(即在参数求值时),没有序列点(除非参数是一个内部包含序列点的表达式,如使用`&&`、`||`或`,`运算符)。
在这种情况下,调用`printf`函数时,读取的先前值既用于确定要存储的值(即`++x`),又用于确定第二个参数的值(即`x+1`)。这明显违反了上述引用的要求,导致未定义行为。
然而,提供一个没有转换说明符的额外参数不会导致未定义行为。如果提供的参数少于转换说明符的数量,或者参数的(提升后的)类型与转换说明符的类型不匹配,那么就会导致未定义行为。但是,传递额外的参数不会导致未定义行为。
要解决这个问题,可以修改代码,确保在序列点之前和之后都没有未定义行为。可以通过将表达式`++x`和`x+1`分开为两个语句来实现。例如,可以将代码改为:
++x; printf("%d", x);
总结起来,问题的原因是在函数参数求值期间没有序列点,导致读取先前值的行为违反了C语言标准的规定。要解决这个问题,可以将表达式分开为两个语句来避免未定义行为。