用C调用函数之前的参数评估顺序
在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 */ }
不,函数参数在C中不按照定义的顺序进行评估。
请参阅Martin York的答案什么是c ++程序员应该知道的所有常见的未定义行为? 。
从C99§6.5.2.2p10开始,函数参数的评估顺序是未指定的:
实际参数中的函数指示符,实际参数和子expression式的评估顺序没有指定,但实际调用之前有一个顺序点。
类似的措词存在于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
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
在C中调用函数参数时可以假设一个评估顺序吗?
不,不能假设,如果是不明确的行为 ,第6.5
节第3
段中的C99标准草案说:
除了后面的规定(对于函数调用(),&&,||,?:和逗号运算符), 子expression式的评估顺序和哪些副作用发生都没有说明。
它还说,除了后面指定的地址和特定的function-call ()
,所以我们稍后会在第6.5.2.2
节的函数调用中看到草案标准:
实际参数中的函数指示符,实际参数和子expression式的评估顺序没有指定 ,但实际调用之前有一个顺序点。
由于您在序列点之间多次修改pa
因此该程序也会显示未定义的行为 。 从标准草案第6.5
节第2
段起:
在前一个序列点和下一个序列点之间,一个对象应该通过评估一个expression式来最多修改一次对象的存储值 。 此外,先前的值应该是只读的,以确定要存储的值。
它引用下面的代码示例为undefined:
i = ++i + 1; a[i++] = i;
重要的是要注意,虽然逗号运算符确实引入了顺序点,但在函数调用中使用的逗号是分隔符而不是comma operator
。 如果我们看第6.5.17
节逗号操作符第2
段说:
逗号运算符的左操作数被评估为voidexpression式; 评估后有一个顺序点。
但第3
段说:
示例如语法所示,逗号运算符(如本子节中所述) 不能出现在使用逗号分隔列表中的项目(如函数参数或初始值设定项列表 )的上下文中 。
不知道这个,用gcc
至less用-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
您可以在这里find这些信息。 一些有用的标志,从长远来看将为您节省很多麻烦,而且对gcc
和clang
都很常见,这些-Wextra -Wconversion -pedantic
是-Wextra -Wconversion -pedantic
。 对于clang
理解-sanitize可以是非常有帮助的。 例如-fsanitize=undefined
将在运行时捕获许多未定义行为的实例。
正如其他人所说,函数参数的评估顺序是未指定的,在评估它们之间没有顺序点。 因为您在传递每个参数时随后更改了pa
,所以您在两个序列点之间更改并读取pa
两次。 这实际上是未定义的行为。 我在GCC手册中find了一个非常好的解释,我认为这可能是有帮助的:
C和C ++标准定义了C / C ++程序中的expression式按顺序点进行评估的顺序,这些顺序点表示了程序各部分执行之间的部分顺序:在顺序点之前执行的那些顺序点,它。 这些是在对一个&&,||,?的第一个操作数进行评估之后,对一个完整的expression式(不是大expression式的一部分)进行评估之后发生的。 :或(逗号)运算符,然后再调用一个函数(但在其参数和expression被调用函数的expression式之后)以及其他某些地方。 除了顺序点规则所expression的以外,还没有规定expression式的子expression式的评估顺序。 所有这些规则只描述了一个部分顺序而不是一个总顺序,例如,如果在一个expression式中调用了两个函数,而它们之间没有顺序点,则没有指定调用函数的顺序。 但是,标准委员会已经裁定函数调用不重叠。
在序列点之间没有指定对对象值的修改生效。 行为依赖于此的程序具有未定义的行为; C和C ++标准规定:“在前一个和下一个序列点之间,一个对象最多应该通过评估一个expression式修改其存储值。 而且,只有在读取之前的值才能确定要存储的值。“ 如果一个程序违反了这些规则,任何特定实现的结果是完全不可预知的。
具有未定义行为的代码的例子是a = a ++; a [n] = b [n ++]和a [i ++] = i ;. 一些更复杂的病例不能被这个选项诊断出来,并且可能会有偶然的假阳性结果,但是总的来说,在程序中检测到这种问题已经被certificate是非常有效的。
标准的措辞混乱,因此在微妙的情况下,序列点规则的确切含义有一些争议。 有关问题讨论的链接,包括提出的正式定义,可以在GCC的阅读页面find, url为http://gcc.gnu.org/readings.html 。
在expression式中多次修改variables是未定义的行为。 所以你可能在不同的编译器上得到不同的结果。 所以不要多次修改一个variables。
格兰特的答案是正确的,它是未定义的。
但,,,
通过你的例子,你的编译器似乎是按照从右到左的顺序进行评估(不出所料,参数被压入堆栈的顺序)。 如果您可以进行其他testing以显示即使启用了优化也保持顺序一致,并且如果只打算使用该版本的编译器,则可以安全地假定从右至左sorting。
虽然这完全是非便携式的,可怕的,可怕的事情。