C 编程/副作用和顺序点
外观
< C 编程
在 C 中,更普遍地说,在计算机科学中,如果函数或表达式修改了其范围之外的状态,或者与其调用函数或外部世界有可观察的交互,则该函数或表达式被称为具有副作用。按照惯例,返回值会影响调用函数,但这通常不被认为是副作用。
一些副作用是
- 修改全局变量或静态变量
- 修改函数参数
- 将数据写入显示器或文件
- 读取数据
- 调用其他具有副作用的函数
在存在副作用的情况下,程序的行为可能取决于历史;也就是说,求值的顺序很重要。了解和调试具有副作用的函数需要了解上下文及其可能的历史。[1][2]
顺序点定义了计算机程序执行中的任何一点,在该点,保证所有先前求值的副作用都已执行,并且尚未执行后续求值的任何副作用。它们在 C 的引用中经常被提及,因为它们是确定表达式的有效性以及如果有效,其可能结果的核心概念。有时需要添加更多顺序点来使表达式定义,并确保唯一的有效求值顺序。
- 一个表达式的求值可以先于另一个表达式的求值,或者等效地,另一个表达式的求值后于第一个表达式的求值。
- 表达式的求值是不确定顺序的,这意味着一个是先于另一个,但哪个是未指定的。
- 表达式的求值是无序的。
无序求值的执行可能重叠,如果它们共享状态,则会导致灾难性的未定义行为。这种情况可能出现在并行计算中,导致竞争条件。
考虑两个函数f()
和g()
。在 C 中,+
运算符没有与顺序点相关联,因此在表达式f()+g()
中,f()
或g()
有可能先执行。逗号运算符引入了顺序点,因此在代码f(),g()
中,求值的顺序是定义的:首先调用f()
,然后调用g()
。
当在单个表达式中多次修改同一个变量时,顺序点也会发挥作用。一个经常被引用的例子是 C 表达式i=i++
,它明显地将i
分配给它之前的值,并递增i
。i
的最终值是不明确的,因为根据表达式求值的顺序,递增可能发生在赋值之前、之后或与赋值交织在一起。特定语言的定义可能指定一种可能的行为,或者只是说行为是未定义的。在 C 中,求值此类表达式会导致未定义行为。[3]
在 C[4]中,顺序点出现在以下位置。
- 在
&&
(逻辑与)、||
(逻辑或)(作为短路求值的一部分)和逗号运算符的左侧和右侧操作数的求值之间。例如,在表达式*p++ != 0 && *q++ != 0
中,*p++ != 0
子表达式的所有副作用在尝试访问q
之前完成。 - 在三元“问号”运算符的第一个操作数和第二个或第三个操作数的求值之间。例如,在表达式
a = (*p++) ? (*p++) : 0
中,在第一个*p++
之后有一个顺序点,这意味着它在执行第二个实例之前已经被递增。 - 在完整表达式结束时。此类别包括表达式语句(例如赋值
a=b;
)、return 语句、if
、switch
、while
或do
-while
语句的控制表达式,以及for
语句中的所有三个表达式。 - 在函数调用中,函数被进入之前。参数求值的顺序没有指定,但此顺序点意味着所有参数的副作用在函数被进入之前都已完成。在表达式
f(i++) + g(j++) + h(k++)
中,f
被调用,参数为i
的原始值,但在进入f
的主体之前,i
被递增。类似地,在进入g
和h
之前,分别更新j
和k
。但是,没有指定f()
、g()
、h()
执行的顺序,也没有指定i
、j
、k
递增的顺序。如果f
的主体访问变量j
和k
,它可能会发现这两个变量都被递增了,或者都没有被递增,或者只有一个变量被递增。(函数调用f(a,b,c)
不是逗号运算符的用法;a
、b
和c
的求值顺序是未指定的。) - 在函数返回时,返回值被复制到调用上下文之后。(此顺序点仅在 C++ 标准中指定;它仅在 C 中隐式存在。)
- 在初始化程序结束时;例如,在声明
int a = 5;
中,在对5
求值之后。 - 在每个声明序列中的每个声明符之间;例如,在
int x = a++, y = a++
中,在对a++
的两次求值之间。(这不是逗号运算符的例子。) - 在与输入/输出格式说明符关联的每个转换之后。例如,在表达式
printf("foo %n %d", &a, 42)
中,在%n
被求值之后,并在打印42
之前,有一个顺序点。
- ↑ “函数式编程研究主题” D. Turner 编著,Addison-Wesley,1990 年,第 17-42 页。检索自:Hughes, John, 为什么函数式编程很重要 (PDF)
- ↑ Collberg, CSc 520 编程语言原理,亚利桑那大学计算机科学系
- ↑ C99 规范的第 6.5 章 #2 条:“在先前和下一个顺序点之间,对象在其存储值最多被表达式求值修改一次。此外,仅访问先前值以确定要存储的值。”
- ↑ C99 规范的附录 C 列出了可以假设顺序点的情况。
- 问题 3.8 的 comp.lang.c 常见问题解答