在C中调用时,可以假设函数参数的评估顺序吗?根据以下程序,我执行时似乎没有特定的顺序。
#include <stdio.h>
int main()
{
int a[] = {1, 2, 3};
int * pa;
pa = &a[0];
printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
/* Result: a[0] = 3 a[1] = 2 a[2] = 2 */
pa = &a[0];
printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa++),*(pa),*(++pa));
/* Result: a[0] = 2 a[1] = 2 a[2] = 2 */
pa = &a[0];
printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa++),*(++pa), *(pa));
/* a[0] = 2 a[1] = 2 a[2] = 1 */
}
答案 0 :(得分:62)
不,函数参数不会按照C中的定义顺序进行评估。
参见Martin York对What are all the common undefined behaviour that c++ programmer should know about?的回答。
答案 1 :(得分:19)
函数参数的评估顺序未指定,来自C99§6.5.2.2p10:
评估的顺序 功能指示符,实际 参数和子表达式 实际参数未指定, 但之前有一个序列点 实际的电话。
C89中存在类似的措辞。
此外,您正在多次修改pa
而没有插入调用未定义行为的序列点(逗号运算符引入了一个序列点,但是分隔函数参数的逗号却没有)。如果你打开编译器上的警告,它应该警告你:
$ gcc -Wall -W -ansi -pedantic test.c -o test
test.c: In function ‘main’:
test.c:9: warning: operation on ‘pa’ may be undefined
test.c:9: warning: operation on ‘pa’ may be undefined
test.c:13: warning: operation on ‘pa’ may be undefined
test.c:13: warning: operation on ‘pa’ may be undefined
test.c:17: warning: operation on ‘pa’ may be undefined
test.c:17: warning: operation on ‘pa’ may be undefined
test.c:20: warning: control reaches end of non-void function
答案 2 :(得分:13)
只是为了增加一些经验 以下代码:
int i=1;
printf("%d %d %d\n", i++, i++, i);
结果
2 1 3
- 在Linux.i686上使用g ++ 4.2.1
1 2 3
- 在Linux.i686上使用SunStudio C ++ 5.9
2 1 3
- 在SunOS.x86pc上使用g ++ 4.2.1
1 2 3
- 在SunOS.x86pc上使用SunStudio C ++ 5.9
1 2 3
- 在SunOS.sun4u上使用g ++ 4.2.1
1 2 3
- 在SunOS.sun4u上使用SunStudio C ++ 5.9
答案 3 :(得分:9)
在C?
中调用时,可以假设函数参数的评估顺序
不,不能假设unspecified behavior部分6.5
段3
中的draft C99 standard是undefined behavior:
运算符和操作数的分组由语法表示.74)除非另有说明 稍后(对于函数调用(),&amp;&amp;,||,?:和逗号运算符),子表达式的评估顺序和副作用发生的顺序都是未指定的。强>
除了后面指定的内容以及专门针对网站function-call ()
之外,它还说明了,因此我们稍后会在6.5.2.2
部分{em>函数调用段10
部分的标准草案中看到表示:
函数指示符的评估顺序,实际参数和 实际参数中的子表达式未指定,但有一个序列点 在实际通话之前。
此计划还展示sequence points,因为您在comma operator之间多次修改pa
。来自草案标准部分6.5
段2
:
在上一个和下一个序列点之间,对象应具有其存储值 通过表达式的评估,最多修改一次。此外,先前的价值 应该只读以确定要存储的值。
它引用以下代码示例为未定义:
i = ++i + 1;
a[i++] = i;
需要注意的是,尽管here确实引入了序列点,但函数调用中使用的逗号是分隔符而不是comma operator
。如果我们查看部分6.5.17
逗号运算符段落2
说:
逗号运算符的左操作数被计算为void表达式; 有一个 评估后的序列点。
但第3
段说:
示例如语法所示,逗号运算符(如本子条款中所述)不能出现在使用逗号分隔列表中的项目的上下文中(例如函数的参数或初始化程序列表强>)。
在不知情的情况下,使用至少gcc
的{{1}}打开警告会提供类似于以下内容的消息:
-Wall
默认warning: operation on 'pa' may be undefined [-Wsequence-point]
printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
^
会发出类似以下消息的警告:
clang
一般来说,了解如何以最有效的方式使用工具非常重要,了解可用于警告的标志非常重要,warning: unsequenced modification and access to 'pa' [-Wunsequenced]
printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
~ ^
您可以找到该信息understanding -fsanitize。从长远来看,一些有用且会为您节省很多麻烦的标记gcc
和gcc
都是clang
。对于-Wextra -Wconversion -pedantic
{{3}}可能非常有帮助。例如,clang
将在运行时捕获许多未定义行为的实例。
答案 4 :(得分:5)
正如其他人已经说过的那样,评估函数参数的顺序是未指定的,并且在评估它们之间没有序列点。因为您在传递每个参数时随后更改pa
,所以您在两个序列点之间更改并读取pa
两次。这实际上是未定义的行为。我在GCC手册中找到了一个非常好的解释,我认为这可能会有所帮助:
C和C ++标准定义了C / C ++程序中表达式按顺序点进行计算的顺序,它表示程序各部分执行之间的部分顺序:在序列点之前执行的顺序,以及那些在它之后执行的。在评估&amp;&amp;,||,?的第一个操作数之后,在评估完整表达式(一个不是较大表达式的一部分)之后发生这些。 :或者,(逗号)运算符,在调用函数之前(但在评估其参数和表示被调用函数的表达式之后),以及某些其他地方。除了由序列点规则表示之外,未指定表达式的子表达式的评估顺序。所有这些规则仅描述部分顺序而不是总顺序,因为,例如,如果在一个表达式中调用两个函数而它们之间没有序列点,则不指定调用函数的顺序。但是,标准委员会已经裁定函数调用不会重叠。
在序列点之间没有指定对对象值的修改生效。行为依赖于此的程序具有未定义的行为; C和C ++标准规定“在上一个和下一个序列点之间,一个对象的表达式的评估最多只能修改一次存储值。此外,先前的值应该是只读的,以确定要存储的值。“如果程序违反了这些规则,那么任何特定实现的结果都是完全不可预测的。
具有未定义行为的代码示例是a = a ++ ;, a [n] = b [n ++]和[i ++] = i;。一些更复杂的病例没有被这个选项诊断出来,并且可能偶尔会出现假阳性结果,但总的来说,它已被发现在程序中检测到这类问题时相当有效。
标准措辞令人困惑,因此在细微情况下对序列点规则的确切含义存在争议。有关问题讨论的链接,包括拟议的正式定义,可在GCC阅读页面http://gcc.gnu.org/readings.html找到。
答案 5 :(得分:1)
在表达式中多次修改变量是未定义的行为。 所以你可能会在不同的编译器上得到不同的结果。因此,请避免多次修改变量。
答案 6 :(得分:-1)
格兰特的回答是正确的,未定义。
BUT ,,,
通过您的示例,您的编译器似乎是按从右到左的顺序进行评估(毫不奇怪,参数被推入堆栈的顺序)。如果您可以进行其他测试以显示即使启用了优化也能保持一致的顺序,并且如果您只是坚持使用该版本的编译器,则可以安全地假设从右到左的顺序。
这完全是非便携式的,可怕的,可怕的事情。