在C中以及更一般地在计算机科学中,如果函数或表达式修改其范围之外的状态或者具有与其调用函数或外部世界的可观察的交互,则称该函数或表达具有副作用。按照惯例,返回值会对调用函数产生影响,但这通常不被视为副作用。
一些副作用是:
- 修改全局变量或静态变量
- 修改函数参数
- 将数据写入显示或文件
- 读数据
- 调用其他副作用函数
在存在副作用的情况下,程序的行为可能取决于历史; 也就是说,评估的顺序很重要。理解和调试具有副作用的函数需要了解有关上下文及其可能的历史。
一个序列点定义了它保证了先前评估的所有副作用将被执行,目前尚未执行从后续评估没有副作用在计算机程序执行的任何点。它们通常在引用C时被提及,因为它们是用于确定有效性的核心概念,并且如果有效则是表达式的可能结果。有时需要添加更多序列点来定义表达式并确保单个有效的评估顺序。
- 表达式的评估可以在另一个表达式之前进行排序,或者相当于另一个表达式的评估在第一个表达式之后进行排序。
- 表达式的评估是不确定的,即一个在另一个之前排序,但是未指定。
- 这些表达的评价是没有统计的。
如果它们共享状态,则未执行的评估的执行可以重叠,具有灾难性的未定义行为。这种情况可能出现在并行计算中,从而导致竞争条件。
歧义的例子
考虑两个功能f()
和g()
。在C中,+
运算符不与序列点相关联,因此在表达式f()+g()
中可能首先执行f()
或g()
将执行。逗号运算符引入了一个序列点,因此在代码f(),g()
中定义了评估顺序:首先f()
调用,然后g()
调用。
当在单个表达式中多次修改相同变量时,序列点也会起作用。一个经常被引用的例子是C表达式i=i++
,它显然都分配了i
它以前的值和增量i
。最终值i
是不明确的,因为根据表达式评估的顺序,增量可能在赋值之前,之后或与赋值交错。特定语言的定义可能指定一种可能的行为,或者只是说行为未定义。在C中,评估这样的表达式会产生不确定的行为。
在C ,序列点出现在以下位置。
- 在评估&&(逻辑AND)的左右操作数之间,|| (逻辑OR)(作为短路评估的一部分)和逗号运算符。例如,在表达式中
*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
,但i
在进入正文之前递增f
。同样,j
和k
正在进入前更新g
和h
分别。然而,不指定以何种顺序f()
,g()
,h()
被执行时,也不能以何种顺序i
,j
,k
递增。如果正文f
访问变量j
和k
,它可能会发现两者都没有,或者只是其中一个增加了。(函数调用f(a,b,c)
是不一个使用逗号运算符的;评价为顺序a
,b
以及c
是不确定的。) - 在函数返回时,将返回值复制到调用上下文中。(此序列点仅在C ++标准中指定;它仅在C中隐式存在。)
- 在初始化器结束时; 例如,在
5
声明中评估之后int a = 5;
。 - 每个声明符序列中的每个声明符之间; 例如,两个评估之间
a++
在int x = a++, y = a++
。(这不是逗号运算符的示例。) - 与输入/输出格式说明符关联的每次转换后。例如,在表达式中
printf("foo %n %d", &a, 42)
,在%n
评估之后和打印之前存在序列点42
。
猜你想读: C编程教科书目录:从简介到C以后全本
THE END