73

在 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 */

}
4

7 回答 7

71

不,函数参数不会按照 C 中定义的顺序进行评估。

请参阅 Martin York 对C++ 程序员应该了解的所有常见未定义行为的回答?.

于 2008-12-17T22:31:20.083 回答
22

未指定函数参数的评估顺序,来自 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
于 2008-12-17T22:46:57.493 回答
17

只是补充一些经验。
以下代码:

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

于 2011-06-15T12:36:28.180 回答
11

在 C 中调用函数参数时,是否可以假定函数参数的评估顺序?

不,不能假设它是未指定的行为C99 标准草案在段落6.5段落中3说:

运算符和操作数的分组由语法指示。74) 除稍后指定(对于函数调用 ()、&&、||、?: 和逗号运算符)外,子表达式的求值顺序和中的顺序发生哪些副作用都未指定。

它还说 except as specified as later and specific sites function-call (),所以我们看到稍后在6.5.2.2 函数调用段落中的标准草案中10说:

函数指示符、实际参数和实际参数中的子表达式的求值顺序是未指定的,但在实际调用之前有一个序列点。

该程序还表现出未定义的行为,因为您在序列点pa之间进行了多次修改。从草案标准部分段落:6.52

在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的评估修改一次。此外,应仅读取先验值以确定要存储的值。

它引用了以下未定义的代码示例:

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找到该信息。一些有用的标志,从长远来看可以为你省去很多麻烦,并且对两者都是通用的。对于理解 -fsanitize可能非常有帮助。例如,将在运行时捕获许多未定义行为的实例。gccclang-Wextra -Wconversion -pedanticclang -fsanitize=undefined

于 2013-08-15T03:04:24.883 回答
6

正如其他人已经说过的,评估函数参数的顺序是未指定的,并且在评估它们之间没有顺序点。因为您pa在传递每个参数时随后进行了更改,所以您pa在两个序列点之间更改并读取了两次。这实际上是未定义的行为。我在 GCC 手册中找到了一个很好的解释,我认为这可能会有所帮助:

C 和 C++ 标准定义了 C/C++ 程序中的表达式根据序列点计算的顺序,序列点表示程序部分执行之间的偏序:在序列点之前执行的部分和在序列点之后执行的部分它。这些发生在对完整表达式(不是更大表达式的一部分)的求值之后,在对 &&、||、? 的第一个操作数求值之后 : 或 ,(逗号)运算符,在调用函数之前(但在对其参数和表示被调用函数的表达式求值之后),以及在某些其他地方。除了由序列点规则表达的以外,没有指定表达式的子表达式的求值顺序。所有这些规则仅描述部分顺序而不是全顺序,因为例如,如果在一个表达式中调用两个函数且它们之间没有序列点,则不指定调用函数的顺序。然而,标准委员会已经裁定函数调用不重叠。

未指定序列点之间对对象值的修改何时生效。行为依赖于此的程序具有未定义的行为;C 和 C++ 标准规定“在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的评估修改一次。此外,应仅读取先验值以确定要存储的值。”。如果一个程序违反了这些规则,任何特定实现的结果都是完全不可预测的。

具有未定义行为的代码示例有 a = a++;、a[n] = b[n++] 和 a[i++] = i;。此选项无法诊断一些更复杂的情况,并且可能偶尔会给出假阳性结果,但通常发现它在检测程序中的此类问题方面相当有效。

该标准的措辞令人困惑,因此在微妙的情况下对序列点规则的确切含义存在一些争论。可以在 GCC 阅读页面http://gcc.gnu.org/readings.html上找到该问题讨论的链接,包括建议的正式定义。

于 2008-12-28T08:53:58.240 回答
1

在表达式中多次修改变量是未定义的行为。所以你可能会在不同的编译器上得到不同的结果。所以避免多次修改一个变量。

于 2014-01-26T10:38:41.963 回答
-1

格兰特的答案是正确的,它是不确定的。

但,,,

通过您的示例,您的编译器似乎以从右到左的顺序进行评估(不出所料,参数被推入堆栈的顺序)。如果您可以进行其他测试以表明即使启用了优化也可以始终如一地维护顺序,并且如果您只坚持使用该编译器的一个版本,则可以安全地假设从右到左的顺序。

不过,这完全是不可移植的,而且是一件可怕的、可怕的事情。

于 2008-12-17T22:49:13.980 回答