Pet*_*ica 18 c++ side-effects undefined-behavior language-lawyer c++17
C++20 标准在Function Call 7.6.1.3/8 中说:
参数的初始化(包括每个关联的值计算和副作用)相对于任何其他参数的初始化是不确定的。
不确定排序(与未排序相反)可确保影响同一内存区域的副作用不是未定义的行为。Cppreference给出了以下示例:
f(i = -2, i = -2); // C++17 之前未定义的行为 f(++i, ++i); // C++17 之前未定义的行为, C++17 之后未指定
C++17 中的变化似乎并未出现在引用的部分中,尽管其措辞在几个世纪以来基本上保持不变。(好的;在 n3337 中这只是一个注释。)
一个简单的示例会引发来自 gcc 和clang的警告:
void f(int fa, int fb);
void m() // contains code calling f()
{
int a = 11;
f(++a, ++a);
cout << "after f(): a=" << a << '\n';
}
Run Code Online (Sandbox Code Playgroud)
<source>:6:7: warning: multiple unsequenced modifications to 'a' [-Wunsequenced]
f(++a, ++a);
^ ~~
Run Code Online (Sandbox Code Playgroud)
gcc 还会生成不直观的代码,a在将其值移入两个参数寄存器之前递增两次。这与我对标准措辞的理解相矛盾。
stackoverflow 用户 Davis Herring在评论中提到,虽然初始化是按顺序进行的,但参数表达式的求值却不是。
我是否误解了这个措辞?cppreference错误吗?编译器是不是错了,尤其是gcc?C++17 中关于函数参数的具体变化是什么(如果有的话)?
如果您的问题是“参数的初始化”是否涉及评估作为其初始值设定项一部分的表达式……当然是这样。初始化参数的工作方式与初始化任何其他对象 ( [dcl.init]/1 )完全相同:
本小节中描述的初始化过程适用于所有初始化,无论语法上下文如何,包括函数参数的初始化([expr.call])、返回值的初始化([stmt.return])或初始化程序时跟随一个声明符。
添加了强调。
[dcl.init] 完整地描述了初始化对象的过程,但在所有情况下,它都涉及评估初始化表达式。因此,这符合排序规则的“每个相关值计算和副作用”部分。
任何不对参数初始值设定项表达式执行此操作的编译器都会出错。
编译器可以警告他们想要的任何内容。事实上,编译器警告通常是技术上有效但不合理的代码。
即使改变这部分标准的要点也不是把琐碎的废话f(++a, ++a)变成半合理的代码。它用于处理代码编写者不知道同一对象在多个位置被引用的情况。考虑:
template<typename ...Args>
void g(Args &&args)
{
f((++args) ...);
}
Run Code Online (Sandbox Code Playgroud)
在 C++17 之前,此代码的有效性完全取决于用户是否传递了对同一对象的两个引用,并且编译器无法合理地警告潜在问题。在 C++17 之后,此代码是安全的(模编译器错误)。
因此,对容易检测到的令人困惑的代码发出警告不仅是有效的,而且是好的。