C/C++ 自增自减的未定义行为

最近更新于 2024-05-05 14:18

Table of Contents

环境


Debian 11 (arm64)
编译:

  • C 语言:编译器 gcc 10.2.1;编译标准 C17;参数:-std=c17 -no-pie -Wall -Werror=return-type -Werror=address -Werror=sequence-point -Werror=format-security -Wextra -pedantic -Wimplicit-fallthrough -Wsequence-point -Wswitch-unreachable -Wswitch-enum -Wstringop-truncation -Wbool-compare -Wtautological-compare -Wfloat-equal -Wshadow=global -Wpointer-arith -Wpointer-compare -Wcast-align -Wcast-qual -Wwrite-strings -Wdangling-else -Wlogical-op -Wconversion -g -O0
  • C++:编译器 g++ 10.2.1;编译标准 C++20;参数:-std=c++20 -no-pie -Wall -Werror=return-type -Werror=non-virtual-dtor -Werror=address -Werror=sequence-point -Werror=format-security -Wextra -pedantic -Wimplicit-fallthrough -Wsequence-point -Wswitch-unreachable -Wswitch-enum -Wstringop-truncation -Wbool-compare -Wtautological-compare -Wfloat-equal -Wshadow=global -Wpointer-arith -Wpointer-compare -Wcast-align -Wcast-qual -Wwrite-strings -Wdangling-else -Wlogical-op -Wconversion -g -O0


Windows 11 专业工作站版 22H2
平台工具集:Visual Studio 2022 v143
编译标准 C17/C++20
警告等级:启用所有警告

注:下面例子中,符合 C/C++ 标准的采用 ① 编译运行,标准未规定的行为(称为 UB,下同),将同时使用 ① 和 ② 编译运行

C 语言

#include <stdio.h>

int main()
{
    int a = 10;
    int b = ++a;
    printf("a=%d,b=%d\n", a, b);
    int c = a++;
    printf("a=%d,c=%d\n", a, c);
    int d = --a;
    printf("a=%d,d=%d\n", a, d);
    int e = a--;
    printf("a=%d,e=%d\n", a, e);

    return 0;
}

file

上面对自增自减的使用是符合规范的使用,操作符在前,赋给变量的值就是自增或自减后的,操作符在后就是赋自增或自减前的值

而下面的情况就是 UB 行为,至少在我这里测试的 C17 和 C++20 中都没有规定

#include <stdio.h>

int main()
{
    int a = 10;
    int b = (++a) + (++a);
    printf("a=%d,b=%d\n", a, b);

    a = 10;
    int c = (a++) + (a++);
    printf("a=%d,c=%d\n", a, c);

    a = 10;
    int d = (++a) + (a++);
    printf("a=%d,d=%d\n", a, d);

    a = 10;
    int e = (--a) + (--a);
    printf("a=%d,e=%d\n", a, e);

    a = 10;
    int f = (a--) + (a--);
    printf("a=%d,f=%d\n", a, f);

    a = 10;
    int g = (--a) + (a--);
    printf("a=%d,g=%d\n", a, g);

    a = 10;
    printf("a=%d,++a=%d,a++=%d\n", a, ++a, a++);

    a = 10;
    printf("a=%d,++a=%d,++a=%d\n", a, ++a, ++a);

    a = 10;
    printf("a=%d,a++=%d,a++=%d\n", a, a++, a++);

    a = 10;
    printf("a=%d,--a=%d,a----=%d\n", a, --a, a--);

    a = 10;
    printf("a=%d,--a=%d,--a--=%d\n", a, --a, --a);

    a = 10;
    printf("a=%d,a--=%d,a--=%d\n", a, a--, a--);
}

环境 ① 编译
file

在 -Werror=sequence-point 参数下就检测到了 UB
在同一个语句中,对同一个变量,多次自增/自减操作,它的值是不确定的。比如 (++a) + (++a),第一个 ++a 的值确实是原来的 a 的值再加一,那么后面那个表达式再次执行 ++a 时,究竟是在原来的 a 上加一,还是在加一后的值上加一,C 语言标准中并没有规定。后面在 printf 中的操作,也是这个道理。
在开发中就要避免这样使用,标准没有规定行为,它的结果就是看编译器怎么做,这样写出来的程序就具有不确定性。你自己写的时候编译运行符合自己的要求,但是把源码给别人编译运行时,用的编译器不同结果就可能会改变。下面就做个对比

现在还是按照环境 ① 编译,但是忽略这种错误检查
file

环境 ② 编译时,即使设置启用了所有警告都没指出这个
file

同样的代码,部分出现了结果不同的情况,即使相同的部分,再换一种编译器说不定可能又会不同,就看编译器怎么做

C++

上面的代码在 C++ 支持的范围内,所以继续使用上面的代码再分别按照 C++ 来编译

环境 ①
file

环境 ②
file

环境 ① 的结果和 C 语言一样,环境 ② 的就有差别了,同一个编译器,按照 C 和 C++ 分别编译运行的结果出现了不同。当然也不能要求它们一样,毕竟 C 和 C++ 是两门不同的语言,各自有各自的标准,再说这个是 UB,怎么做是编译器的自由,只是说微软的 MSVC 在 C 和 C++ 的这种 UB 处理上使用了不同的逻辑。

C/C++ 自增自减的未定义行为
Scroll to top