[C语言学习] 由a = a++谈谈C语言中的序列点

今天在复习C语言中的自增运算符++和自减运算符时,想到了这样一个问题。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(void)
{
    int a = 0;

    a = a++;

    printf("%d", a);

    return 0;
}

我们发现,在Microsoft Visual Studio 2019DevC++(使用GCC 4.9.2)上,得出的结果是竟然是不同的。

仔细思考可以发现,问题的关键在于:是自增先进行还是赋值先进行呢?

如果是自增在赋值前,那就是先a自增变成1,再把(a++)的值0赋给a,所以最后是a的值为0;

如果是自增在赋值后,那就是先把(a++)的值0赋给a,再把a自增成1,所以最后是a的值为1。

这个问题究竟是怎么回事呢?在回答之前,我们先来讲一讲C语言中的序列点。


副作用和序列点

副作用 (side effect)

副作用是对数据对象或文件的修改。比如下面的语句:

1
a = 666;

它的副作用是将a的值变为666。这里可能会令人费解,因为这似乎是语句的“主要作用”。但实际上,从C语言的角度理解该语句的主要作用是对表达式求值。类似的,对于函数printf(),其副作用是格式化输出。

序列点 (sequence point)

序列点是程序执行的点,在该点上,所有的副作用都在进入下一步之前发生。比如:

1
(a++) > 0 && a < 10

在执行a < 10前,必须完成a++的副作用,即a自增。因为&&的左边存在序列点。

常见的序列点:

1.逻辑||和逻辑&&

2.逗号运算符,

3.三元操作符?

4.语句结束后的;

5.完整表达式(即这个表达式不是另一个更大表达式的子表达式)的结束

例如,考虑如下代码:

1
2
while (guests++ < 10)
    printf("%d\n", guests);

这里,每次printf()输出的guests都是已经自增过的。因为完整表达式guests++ < 10后有序列点。


回到最初的问题:为什么不同的编译器编译出的程序给出了不同的结果?

实际上,a = a++这种表达式属于C语言中未定义的行为(Undefined Behavior)。结合刚才学习的序列点来看,在a = a++这个表达式中没有序列点,即这个表达式处于两个序列点之间。在这种情况下,编译器无论对顺序怎样处理都是被允许的。

在编程中,我们必须避免出现未定义的行为。下面给出的这些语句也属于未定义的行为:

1
2
num = (i++) + (i++) + (i++);
y = (4 + x++) + (6 + x++);

参考

Stephen Prata, C Primer Plus (6th Edition)

Tsroad, 谈谈C语言中的序列点(sequence point)和副作用(side effects)


版权声明:本文为Myth原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.mythologyli.cn/2019/12/06/213/

滚动到顶部