在 C 中调用函数参数时,是否可以假定函数参数的评估顺序?
不,不能假设它是未指定的行为,C99 标准草案在段落6.5
段落中3
说:
运算符和操作数的分组由语法指示。74) 除稍后指定(对于函数调用 ()、&&、||、?: 和逗号运算符)外,子表达式的求值顺序和中的顺序发生哪些副作用都未指定。
它还说 except as specified as later and specific sites 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
找到该信息。一些有用的标志,从长远来看可以为你省去很多麻烦,并且对两者都是通用的。对于理解 -fsanitize可能非常有帮助。例如,将在运行时捕获许多未定义行为的实例。gcc
clang
-Wextra -Wconversion -pedantic
clang
-fsanitize=undefined