物流官网网站,今天新闻联播,男女做羞羞的故事网站,企业宣传网站在哪里做你知道为什么 i i 2 在 C17 前行为未定义吗#xff1f;
你知道为什么 func(a(), b()) 中#xff0c;a() 与 b() 的执行顺序为什么不是确定的吗#xff1f;
这篇文章可以解答你的疑惑。 注#xff1a;内容中包含作者自行理解的表述#xff0c;请谨慎参考。 表达式求值 …你知道为什么 i i 2 在 C17 前行为未定义吗
你知道为什么 func(a(), b()) 中a() 与 b() 的执行顺序为什么不是确定的吗
这篇文章可以解答你的疑惑。 注内容中包含作者自行理解的表述请谨慎参考。 表达式求值
每个表达式的求值包括
值计算计算表达式所返回的值。引发副作用访问读或写volatile 泛左值所指代的对象修改写入对象调用库 I/O 函数或调用任何做出这些操作的函数。
表达式副作用访问 volatile 对象可能将寄存器中的该变量值更新为内存中的值修改对象除了对象被修改程序的状态也有略微改变I/O对外部环境产生影响包括上述所有表达式的函数如上
例如表达式 a值计算指这个表达式的返回值是 a引发副作用指 a 的值加了 1。但在这个例子中副作用是已定义的是用户期望的。
顺序
按顺序早于sequenced before
A 按顺序早于 B等价地B 按顺序晚于 A则 A 的求值会在 B 的求值开始前完成。A 不按顺序早于 B 而 B 不按顺序早于 A 无顺序unsequenced则它们以任何顺序进行并且同线程内编译器可以将两者 CPU 指令交错。顺序不确定indeterminately sequenced它们以任何顺序进行但不可重叠。下次求值顺序可以相反。 注意无顺序和顺序不确定的区别。由此推出顺序不确定和早于都属于已定义的顺序。只有无顺序是未定义的。 求值顺序规则 将在另一篇文章中说明以下相关知识点完整表达式、弃值表达式、成分表达式子表达式、指名函数表达式、成员指针表达式、复合赋值表达式。 编号表达式类型操作早于对象1完整表达式值计算、副作用下一个完整表达式2运算符的操作数值计算运算符结果3函数实参值计算、副作用函数体内任何语句4内建后自增、后自减值计算副作用5内建前自增、前自减副作用值计算6 || ,逗号的左操作数值计算、副作用右操作数7与条件运算符?:第一表达式值计算、副作用第二、三表达式8内建赋值运算符、复合赋值运算符的左右操作数值计算副作用修改左操作数8.1内建赋值运算符、复合赋值运算符副作用修改左操作数值计算返回引用之前9列表初始化的子句值计算、副作用其后的子句10函数调用表达式中指名函数表达式值计算、副作用 C17起实参、默认形参11下标表达式 E1[E2] 中E1值计算、副作用 C17起E212成员指针表达式 E1.*E2 或 E1-*E2 中E1值计算、副作用 C17起E213移位运算符表达式 E1 E2 或 E1 E2 中E1值计算、副作用 C17起E214简单赋值表达式 E1 E2 或复合赋值表达式 E1 E2 中E2值计算、副作用 C17起E1
如果一个函数调用和一个表达式可以是另一个函数调用没有明确顺序二者的求值顺序不确定。程序必须表现为如同一次函数调用的 CPU 指令不会与其它表达式求值指令交错。C17起std::execution::par_unseq 例外new() 的调用相对于 new 表达式中各实参的求值是顺序不确定的C17前顺序早于它C17起。函数返回值的复制按顺序早于 return 语句 末尾处对所有临时量的销毁。后者又早于该块中所有局部变量的销毁。int add(int a, int b) {return a b;
}int main() {int c add(1, 2);
}add() 的结果复制到 c 早于 ab 结果临时量的销毁
后者又早于局部变量 a,b 的销毁 C17起 函数调用中每个形参的值计算和副作用二者作为一个整体与其它任何形参相比是顺序不确定的。C17起 重载的运算符遵循它所重载的内建运算符的定序规则。C17起 列表初始化逗号分隔的每个表达式如同函数调用一般求值顺序不确定。 未定义行为 注切记未定义顺序和顺序不确定的区别。 如果某个内存位置上的一项副作用相对于同一个内存位置上的另一副作用是无顺序的那么它的行为未定义。 例 1
i i 2; // OKi 副作用早于求值第 5 条等号右边的值计算早于左边副作用赋值第 8 条。执行顺序为 i 副作用 i 求值 i 2 求值 i i 2 副作用 i i 2 求值。 例 2
i i 2; // C17 前行为未定义C17前i 求值早于副作用第 6 条两边的值计算早于 i i 2 的副作用第 8 条而没有定义两个副作用的顺序故而 i 值不确定行为未定义。 C17起i 的副作用早于 i i 2 的副作用第 14 条。因此可能“可能”是因为i 与 2 的求值顺序是不确定的的执行顺序为 i 求值 i 2 求值 i 副作用 i i 2 副作用 i i 2 求值 例 3
f(i -2, i -2); // C17 前行为未定义C17新增了一项规则第 18 条它规定了形参求值和副作用是顺序不确定的此前未定义。此前两个 i -2 不仅顺序不一定甚至 CPU 指令可能交错。此后尽管顺序不确定但 CPU 指令不会交错。因此执行顺序总是 i -2 副作用 i -2 求值 i -2 副作用 i -2 求值 f(i -2, i -2) 副作用 f(i -2, i -2) 求值 例 4
f(i, i); // C17 前行为未定义C17 起未指明同上C17前i 和 i 的副作用顺序是未定义的。C17后第 18 条不管编译器优先执行哪个 i 都是符合规则的。但由于两个 i 顺序不确定所以该表达式的值计算和副作用未明确。 例 5
i i i; // 行为未定义同例 2i 与 i i i 的副作用顺序未定义。
序列点规则C11前
C11 前没有 C11起一般完备的规则表达式求值的顺序规定依靠序列点定义。
C11前的定义
序列点 (sequence point)是执行序列中的点在该点所有来自序列中先前求值的副作用都已经完成而后继求值的副作用都尚未开始。
C11前的规则 每个完整表达式结尾典型地在分号处有一个序列点。 调用函数时无论该函数是否内联无论是否使用函数调用语法所有函数实参的求值若存在之后有一个序列点它发生于函数体内的任何表达式或语句的执行之前。 在从函数返回时在从函数调用结果的复制初始化之后和 return 语句的 表达式 末尾的临时对象析构若存在前有一个序列点。 对函数的返回值进行复制之后并在函数外任何表达式的执行之前有一个序列点。 一旦函数执行开始则在被调用函数的执行完成前不求值调用方函数的任何表达式函数不能交错执行。 每个使用内建非重载运算符的下列四种表达式的求值中表达式 a 的求值后有一个序列点。
a b
a || b
a ? b : c
a , bC11前的未定义行为
前后序列点间至多可以修改在同一个内存位置中的任何对象的存储值一次否则行为未定义。
i i i; // 未定义行为
i i 1; // 未定义行为
i i 1; // 未定义行为i; // 未定义行为
f(i, i); // 未定义行为
f(i -1, i -1); // 未定义行为前后序列点间访问表达式求值所修改的在同一个内存位置中的任何对象的先前值必须只为确定要存储的值。如果以其他任何方式访问那么行为未定义。
cout i i; // 未定义行为
a[i] i; // 未定义行为