为什么 printf ("%d%d%d", ++i, i, i++) 是未定义的行为?

Why printf ("%d%d%d", ++i, i, i++) is undefined behavior?

ISO/IEC 9899(TC2) §6.5 — 2 个表达式告诉我们:

Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be read only to determine the value to be stored.

这是我牢记、记住并且会告诉任何问我为什么标题中的行给出意外输出的人。

但是今天我才发现这一行:

§7.19.6 — 1 个格式化的 input/output 函数:

The formatted input/output functions shall behave as if there is a sequence point after the actions associated with each specifier.

这让我假设:

同时

int i = 0;
printf ("%d, %d", ++i, i++);

应该是未定义的,下一个例子应该可以通过提到的子句:

int i = 0;
printf ("%d, %d, %d", ++i, i, i++);

但输出是:

2, 2, 0

我从未见过更好的示例来说明未定义的行为。

但是为什么呢? 如果该子句为真

"[...]behave as if there is a sequence point after the actions associated with each specifier."

然后将第 6.5 节下的规则应用到与说明符关联的每个动作上,这不会让我们违反该规则,如:

(SP代表一个相关序列点)

SP1++iSP2iSP3i++

从 SP1 开始,在上一个和下一个 SP 之间的给定范围内,++ii 存储值的唯一修改。

从 SP2 开始,前一个和下一个 SP 之间的范围是:++ii,其中 ++i 仍然是该值的唯一修改。

如果我们现在采用 SP3,则前一个 SP (SP2) 和下一个 SP(调用结束)之间发生的所有事情是:

ii++仍然只是i在上一个和下一个SP之间的整个范围内的单个修改。

关于序列点的工作方式,我在这里解释错了什么?

问题不在于与说明符关联的操作。问题在于 printf 函数的参数计算在第一个说明符执行任何操作之前完成。

想象一下如果代码是:

void foo (int i1, int i2, int i3)
{
     printf("%d, %d, %d", i1, i2, i2);
}

foo (++i, i, i++);

到这里就很清楚了。而且包装器不会改变任何东西。