逗号分隔的相互初始化?

11

以下内容是否完全定义:

int x = 42, y = x;

即严格等同于:

int x = 42;
int y = x;

编辑:问题不是关于风格(我知道这是错误的...),问题是“理论上的”


2
请参见:https://www.securecoding.cert.org/confluence/display/seccode/DCL04-C.+Do+not+declare+more+than+one+variable+per+declaration - Damon
1
@Deduplicator 为什么不行呢?每个声明符不是都有顺序吗? - juanchopanza
3
相关吗?http://www.open-std.org/JTC1/SC22/WG21/docs/cwg_active.html#1342 - dyp
1
@LightnessRacesinOrbit:鉴于许多人认为OP问题中的代码具有未定义行为,那么需要单独声明的代码风格标准真的很愚蠢吗? - Michael Burr
1
@Damon:我计划与其他人一起工作,他们都是程序员。 - Lightness Races in Orbit
显示剩余7条评论
2个回答

9

正确答案是

int x = 42, y = x;

int x = 42;
int y = x;

通常是等价的(不是严格的)。

考虑标准§ 8声明符[dcl.decl]:

3每个声明中的初始化声明符都被单独分析,就好像它在一个声明中。

并且脚注[100]进一步解释:

具有多个声明符的声明通常等效于相应的具有单个声明符的声明序列。也就是说

T D1,D2,... Dn;

通常相当于

T D1; T D2; ... T Dn;

其中T是一个decl-specifier-seq,而每个Di都是一个init-declarator。

  • 以上保证x = 42y = x会单独进行评估。但是,正如@Praetorian在评论中正确指出的那样,脚注并不具有规范性。

  • 这意味着评估顺序没有定义,并且实现者也可以按相反的顺序实现声明的评估(即T Dn; ...T D2; T D1;)。

  • 有人可能会认为逗号运算符保证从左到右进行评估。然而,事实并非如此。根据K&R[K&R II,3.6 p.63]的说法,这也适用于C ++:

分隔函数参数、声明变量等的逗号不是逗号运算符,并且不保证从左到右进行评估。


2
脚注不是规范性的。我认为脚注中的“通常”留下了足够的余地,使得实现可以按相反的顺序评估它们,仍然符合规范。 - Praetorian
@Praetorian 我同意脚注的非规范性,但标准中的引用明确指出它们是单独分析的。 - 101010
3
“逗号运算符”是什么?但这里不是指逗号运算符,它只是“init-declarators”的分隔符——在声明中出现的“init-declarator-list”是由逗号分隔的声明符序列。 - Praetorian
1
“通常等价,但不是严格的” - 这意味着 y 有可能没有被初始化为 42 吗? - M.M
@Christophe y = x, x = 42 不是一个有效的表达式。因此,在这种情况下,编译器会生成一个错误。 - 101010
显示剩余6条评论

5
这个问题在很久以前出现在comp.lang.c++.moderated下的主题init-declarator-list analysis order中,结论是Yes
虽然我看到了full-expression参数,但我没有看到求值顺序的参数。所以我认为这是未指定的。
问题的相关部分是:

在这个声明和定义中:

int a = 2, b = a;

是否保证b总是初始化为2?如果是,那么我们可以说a = 2总是在b = a之前进行分析(或评估)吗?

答案的相关部分是:

是的。严格来说,程序的可观察行为必须是作为声明的'a = 2'部分的所有副作用在'b = a'部分的评估开始之前发生的。 (当然,在这个简单的例子中,编译器可以以任何顺序甚至并行地将2分配给a和b,因为这样做会导致相同的可观察行为。)

并且更进一步:

但是,在这种特殊情况下,它确实将声明符列表分为单独的声明符;每个声明符都包含一个完整的表达式,并且按顺序评估声明符。

更新 每个init-declator成为一个full expression的原因很微妙,但据我所知,遵循了我在Are multiple mutations of the same variable within initializer lists undefined behavior pre C++11中使用的相同逻辑。在这种情况下,我们从第8节中定义的语法开始:
init-declarator-list:
  init-declarator
  init-declarator-list , init-declarator
init-declarator:
  declarator initializeropt

下一个焦点是初始化程序语法,它在第8.5节中介绍:
initializer:
  brace-or-equal-initializer
  ( expression-list )
brace-or-equal-initializer:
  = initializer-clause
  braced-init-list
initializer-clause:
  assignment-expression
  braced-init-list

在这两种情况下,我们都有=初始化表达式,它将带我们到赋值表达式,如果我们遵循第5节中的语法,则会带我们回到基本表达式,这可以给我们一个字面量标识符表达式
因此,我们确实有由语法逗号分隔的完整表达式,所以我们有:
int x = 42, y = x;
          ^      ^
          |      end full-expression
          end full-expression

根据第1.9段第14款,我们可以看到:

与完整表达式相关的每个值计算和副作用都在下一个要评估的完整表达式相关的每个值计算和副作用之前被排序。8.

至于评估顺序,我认为这没有明确规定,在初始化程序列表的缺陷报告430中适用于相同逻辑也将适用于此处。在C++11中,初始化程序列表的语言已通过在第8.5.4节中添加以下内容进行修复:

在大括号初始化列表的初始化程序列表中,包括任何由打包扩展(14.5.3)产生的初始化程序子句在内,按它们出现的顺序进行评估。[...]

对于initializer,不存在这样的等效物。

我现在更喜欢它,因为它详细说明了标准的内容。然而,我不确定你如何反驳我的原始批评:初始化程序是一个表达式,而不是初始化。 - dyp
初始化器是一个声明符而不是表达式,因为它不在第5节中出现,但它可以包含一个表达式,每个表达式都将是完整的表达式,因为它不是子表达式。 - Shafik Yaghmour
是的,完全正确,这就是我的全部观点 :) 标准在哪里保证初始化按顺序进行? - dyp
但是 x 的初始化既不是完整表达式 42 的一部分,也不是表达式的副作用,对吗? - dyp
@dyp 我想知道第 3.3.2 p1 节是否意味着顺序,我可以理解为它可能被读作那样。随后的段落也似乎暗示了一种顺序。 - Shafik Yaghmour
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接