在C语言中,调用函数时不能假定函数参数的评估顺序。如果这样做,它将是未指定行为。据C99草案标准第6.5
节第3
段所述:
运算符和操作数的分组由语法表示。除非特别指定(针对函数调用(),&&,||,?:和逗号运算符),子表达式的评估顺序和副作用发生的顺序都是未指定的。
它还说,除非特别指定,并具体引用了函数调用()
,因此我们看到在草案标准的第6.5.2.2
节函数调用的第10
段中,它明确提到:
函数设计器、实际参数和实际参数中的子表达式的求值顺序是未指定的,但在实际调用之前有一个序列点。
由于在序列点之间多次修改了pa
,因此该程序还表现出未定义行为。根据草案标准第6.5
段第2
款:
在前一个和下一个序列点之间,一个对象的存储值最多只能被表达式的求值修改一次。此外,先前的值只能被读取以确定要存储的值。
它引用以下代码示例作为未定义的:
i = ++i + 1;
a[i++] = i;
重要的是要注意,虽然
逗号运算符确实引入了序列点,但在函数调用中使用的逗号是分隔符,而不是
逗号运算符
。如果我们看一下第
6.5.17
节
逗号运算符的第
2
段,它说:
逗号运算符的左操作数被评估为void表达式;在其评估之后有一个序列点。
但第3
段说:
例如,正如语法所示,逗号运算符(如本子句所述)不能出现在逗号用于分隔列表项(如函数参数或初始化列表)的上下文中。
如果不知道这一点,在使用至少-Wall
的gcc
打开警告时将提供类似以下的消息:
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
,您可以在这里找到该信息。一些有用且能够在长期运行中节省大量麻烦的标志是-Wextra -Wconversion -pedantic
,它们对于gcc
和clang
都是常见的。对于clang
,理解-fsanitize可能非常有帮助。例如,-fsanitize=undefined
将在运行时捕获许多未定义行为的实例。
pa =&a [0];
可以简化为pa = a;
,因为a
会衰减为指向其第一个元素的指针。 - RobertS supports Monica Cellio