这是未定义的C行为吗?

use*_*279 35 c undefined-behavior

我们班的C编程教授问了这个问题:

你得到的代码:

int x=1;
printf("%d",++x,x+1);
Run Code Online (Sandbox Code Playgroud)

它总会产生什么输出?

大多数学生说未定义的行为.任何人都可以帮助我理解为什么会这样吗?

感谢编辑和答案,但我仍然感到困惑.

Jer*_*fin 40

在每种合理的情况下,输出可能是2.实际上,你所拥有的未定义的行为.

具体来说,标准说:

在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算来修改一次.此外,先前的值应该只读以确定要存储的值.

评估函数的参数之前有一个序列点,并且评估了所有参数之后的序列点(但是尚未调用的函数).在这两者之间(即,在评估参数时),没有序列点(除非参数是表达式在内部包含一个,例如使用&& ||,运算符).

这意味着该呼叫到printf被读取的先前值二者来确定所存储的值(即,++x),以确定第二个参数的值(即,x+1).这显然违反了上面引用的要求,导致了不确定的行为.

事实上,您提供了一个额外的参数,没有给出转换说明符,这不会导致未定义的行为.如果您提供转换说明符参数较少,或者参数的(提升的)类型与转换说明符的参数不一致,则会得到未定义的行为 - 但是传递额外的参数则不会.

  • @ShinTakezou:你错了; 一致的实现完全可以打印"42".这真的不太可能.我也不相信您的想象力涵盖所有可能的编译器实现. (7认同)
  • @Chris:你在哪里看到副作用的提及:"此外,先前的值应该是只读的,以确定要存储的值"?缺乏这样的限制,这适用于所有代码. (4认同)
  • `输出可能是2`.*可能*字的+1.:) (3认同)
  • @Chris:仔细阅读这篇文章:http://c-faq.com/expr/seqpoints.html (3认同)
  • @Jerry:恭喜您写下正确而明确的答案! (2认同)

Gil*_*il' 14

任何时候程序的行为都是不确定的,任何事情都可能发生 - 经典的短语是"恶魔可能会飞出你的鼻子" - 尽管大多数实现都没有那么远.

函数的参数在概念上是并行评估的(技术术语是它们的评估之间没有序列点).这意味着表达++xx+1可能的顺序进行评价,以相反的顺序,或以某种方式交错.修改变量并尝试并行访问其值时,行为未定义.

对于许多实现,参数是按顺序计算的(尽管并不总是从左到右).所以你不太可能在现实世界中看到任何东西.

但是,编译器可以生成如下代码:

  1. 将x加载到寄存器中r1.
  2. x+1通过添加1来计算r1.
  3. ++x通过添加1来计算r1.那没关系,因为x已加载r1.鉴于编译器的设计方式,步骤2不能进行修改r1,因为只有x在两个序列点之间读取和写入时才会发生这种情况.这是C标准禁止的.
  4. 存储r1x.

在这个(假设但正确的)编译器上,程序将打印3.

(编辑:传递一个额外的参数printf是正确的(N1256中的§7.19.6.1-2 ;感谢Prasoon Saurav)指出这一点.另外:添加一个例子.)

  • 首先,格式为"%d"的printf需要一个整数参数,但是你已经传递了两个.行为未定义.不它不是.仅评估第二个参数,但由于这个原因,行为未定义.注意:printf("%d%d",x ++);未定义,因为格式说明符的数量大于参数的数量. (5认同)
  • @Prasoon:谢谢,你是对的.我已经纠正了我的回答. (2认同)

AnT*_*AnT 11

正确答案是:代码产生未定义的行为.

该行为是未定义的原因是,两个表达式++xx + 1正在修改x和读取x用于不相关的(变形例)的原因,并且这两个动作没有被序列点分离.这导致C(和C++)中的未定义行为.要求以6.5/2的C语言标准给出.

请注意,在这种情况下,未定义的行为与printf函数只给出一个格式说明符和两个实际参数这一事实完全无关 .为了给出更多的参数,printf格式字符串中的格式说明符在C语言中是完全合法的.同样,问题源于违反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));
Run Code Online (Sandbox Code Playgroud)

上面的代码与原始代码"等效",除了涉及我们的操作x被包装到函数中.在这个最新的例子中会发生什么?

此代码中没有未定义的行为.但由于printf参数的评估顺序未指定,因此该代码会产生未指定的行为,即可能将printf其称为printf("%d", 2, 2)或作为printf("%d", 2, 3).在这两种情况下,输出确实是2.但是,此变体的重要区别在于,所有访问x都包含在每个函数的开头和结尾处出现的序列点中,因此此变体不会产生未定义的行为.

这正是其他一些海报试图强迫原始例子的原因.但它无法完成.原始示例产生未定义的行为,这是一个完全不同的野兽.他们显然试图坚持在实践中未定义的行为总是等同于未指明的行为.这是一个完全虚假的主张,只表明制造它的人缺乏专业知识.原始代码产生未定义的行为,句点.

要继续该示例,我们将以前的代码示例修改为

printf("%d %d", inc_x(&x), x_plus_1(x));
Run Code Online (Sandbox Code Playgroud)

代码的输出通常是不可预测的.它可以打印2 2或打印2 3.但请注意,即使行为不可预测,它仍然不会产生未定义的行为.行为未指定,位未定义.未指定的行为仅限于两种可能性:2 2或者2 3.未定义的行为不限于任何内容.它可以格式化硬盘而不是打印东西.感到不同.

  • @ShinTakezou:该标准对符合程序和符合实现的限制设置了所有C语言用户的利益.如果你拒绝对C语言中的核心概念使用与其他人相同的定义那么你不同意大多数其他人对行为是否未定义或者输出是否总是为2而且没有任何意义也就不足为奇了那个论点.其他人从你没有使用的定义开始. (3认同)
  • @ShinTakezou:不正确.首先,当行为是*undefined*时,*任何*都可能发生.所以,你关于什么可以和什么不可能发生的断言没有任何关系.其次,即使行为定义为"x + 1",只要效果满足规范,就可以用"x"做任何事情.是的,`x + 1`*可以*修改`x`,只要它之后将其恢复为原始值.吉尔斯的答案就是这种评价的一个例子. (2认同)
  • @ShinTakezou:第三,你说的是"因为我们不能说首先评估哪一种行为".这表明您不理解*undefined*和*unspecified*行为之间的区别.你将*undefined*行为误认为*未指定的*行为,并因此错误而得出无意义的结论.在这种情况下,未定义的行为与首先评估的内容绝对没有任何关系*. (2认同)
  • @ShinTakezou:关于你拒绝理解标准术语......好吧,这正是你无法想出(或接受)这个问题的正确答案的原因. (2认同)

Vla*_*mir 2

大多数学生表示行为未定义。谁能帮助我理解为什么会这样?

因为函数参数的计算顺序没有指定。

  • 这是无关紧要的;事实上,有一个“++x”和“x+1”而没有中间的序列点,这就是它未定义的原因。 (5认同)