在C中调用时,可以假设函数参数的评估顺序吗?根据以下程序,我执行时似乎没有特定的顺序.
#includeint 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 */ }
Grant Wagner.. 63
不,函数参数不会在C中按照定义的顺序进行评估.
请参阅Martin York的回答c ++程序员应该了解的所有常见的未定义行为是什么?.
不,函数参数不会在C中按照定义的顺序进行评估.
请参阅Martin York的回答c ++程序员应该了解的所有常见的未定义行为是什么?.
函数参数的评估顺序未指定,来自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
只是为了增加一些经验.
以下代码:
int i=1; printf("%d %d %d\n", i++, i++, i);
结果是
2 1 3
- 在Linux.i686上使用g ++ 4.2.1 - 在Linux.i686上
1 2 3
使用SunStudio C++ 5.9 - 在SunOS.x86pc上
2 1 3
使用g ++ 4.2.1 - 在SunOS.x86pc上
1 2 3
使用SunStudio C++ 5.9 - 在SunOS.sun4u上
1 2 3
使用g ++ 4.2.1
1 2 3
- 使用SunOS C++ 5.9在SunOS.sun4u上
在C中调用时,可以假设函数参数的评估顺序吗?
不,不能假设,如果是未指明的行为,则CII标准草案中的段落6.5
段落3
说:
运算符和操作数的分组由语法表示.74)除了稍后指定的(对于函数调用(),&&,||,?:和逗号运算符),子表达式的评估顺序和顺序发生哪些副作用都是未指明的.
除了后面指定的以及特定的站点之外,它还说明了function-call ()
,所以我们在后面的6.5.2.2
函数调用段落中的草案标准中看到10
:
该功能指示符,实际的论点,并且实际的参数中的子表达式的计算顺序是不确定的,但实际调用之前的顺序点.
由于您在序列点之间进行了多次修改,因此该程序还会显示未定义的行为.从草案标准部分段落:pa
6.5
2
在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算来修改一次.此外,先前的值应该只读以确定要存储的值.
它引用以下代码示例为未定义:
i = ++i + 1; a[i++] = i;
需要注意的是,虽然逗号运算符确实引入了序列点,但函数调用中使用的逗号是分隔符,而不是comma operator
.如果我们看一下6.5.17
逗号运算符段落2
说:
逗号运算符的左操作数被计算为void表达式; 评估后有一个序列点.
但段落3
说:
示例如语法所示,逗号运算符(如本子条款中所述)不能出现在使用逗号分隔列表中的项目的上下文中(例如函数的参数或初始化程序列表).
不知道这一点,gcc
至少使用警告打开-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)); ~ ^
一般来说,了解如何以最有效的方式使用工具非常重要,了解可用于警告的标志非常重要,因为gcc
您可以在此处找到相关信息.有些标志是有用的,会为你节省很多的麻烦,从长远来看并是共同的gcc
和clang
是-Wextra -Wconversion -pedantic
.为了clang
理解-fsanitize可以是非常有帮助的.例如,-fsanitize=undefined
将在运行时捕获许多未定义行为的实例.
正如其他人已经说过的那样,评估函数参数的顺序是未指定的,并且在评估它们之间没有序列点.因为您pa
在传递每个参数时随后更改pa
,所以在两个序列点之间更改并读取两次.这实际上是未定义的行为.我在GCC手册中找到了一个非常好的解释,我认为这可能会有所帮助:
C和C++标准定义了C/C++程序中表达式按顺序点进行计算的顺序,它表示程序各部分执行之间的部分顺序:在序列点之前执行的顺序和执行之后执行的顺序它.在评估了&&,||,?的第一个操作数之后,在完整表达式(一个不是较大表达式的一部分)的评估之后发生了这些.:或者,(逗号)运算符,在调用函数之前(但在评估其参数和表示被调用函数的表达式之后),以及某些其他地方.除了由序列点规则表示之外,未指定表达式的子表达式的评估顺序.所有这些规则仅描述部分订单而不是总订单,例如,如果在一个表达式中调用两个函数且它们之间没有序列点,则不指定调用函数的顺序.但是,标准委员会已经裁定函数调用不重叠.
在序列点之间没有指定对对象值的修改生效.行为依赖于此的程序具有未定义的行为; C和C++标准规定"在上一个和下一个序列点之间,一个对象的表达式的评估最多只能修改一次存储值.此外,先前的值应该是只读的,以确定要存储的值." 如果程序违反了这些规则,那么任何特定实现的结果都是完全不可预测的.
具有未定义行为的代码示例是a = a ++ ;, a [n] = b [n ++]和[i ++] = i;.一些更复杂的病例没有被这个选项诊断出来,并且它可能偶尔会出现假阳性结果,但一般来说,它已被发现在检测程序中的这类问题时相当有效.
该标准令人困惑,因此在细微的情况下对序列点规则的确切含义存在争议.有关问题讨论的链接,包括拟议的正式定义,可在GCC阅读页面http://gcc.gnu.org/readings.html上找到.