初学C程序Function Call stack, sequence point(sequencing)的疑问

Beginner's query about C program Function Call stack, sequence point(sequencing)

下面的代码在 Code::Blocks 上 运行 编译时显示不同的结果。

void sum(int a,int b){
    printf("a=%d b=%d\n",a,b);
}
int main(){
    int i=1;
    sum(i=5,++i);
    printf("i=%d\n\n",i);
    /***********************/
    i=2;
    sum(i=5,i++);
    printf("i=%d\n\n",i);
    /**********************/
    i=3;
    sum(i=5,i);
    printf("i=%d\n\n",i);
    return 0;
}

输出:

a=5 b=5
i=5

a=5 b=2
i=5

a=5 b=5
i=5

我认为这个问题的答案与序列点有关,而序列点与此处的++运算符有关。 GCC 必须遵循以固定顺序将值传递给堆栈的命令,但由于 ++,答案不同。我觉得初学者写这样的函数调用不是很常见,但是关于运算符的课程很笼统,可以试试。

我的问题是,它的确切答案应该是什么以及类似的问题?在编译的哪个阶段决定这些事情(明确或不清楚)?涉及哪些特定算法(用于优化或一般)?同一个编译器可以为这样的表达式或语句提供不同的结果吗?最后一个是,初学者如何理解和弄清楚这些问题?有时候很意外。

操作的顺序是在编译的多个阶段决定的,这就是导致您看到奇怪结果的原因。特别是在优化阶段,编译器可以以并不总是显而易见的方式重新排序代码,在这种情况下它会影响结果(这很好,因为你正在做一些未定义的事情并且编译器明确允许做任何它想做的事情使用该代码)。不涉及任何特定算法,它是在不同点应用的几种不同算法之间的交互,并且在每个点应用的算法可能会有所不同,具体取决于编译器决定的是处理特定代码位的最佳方式。

当文档谈到未定义的行为时,未定义的不是特定编译器的行为,而是编译器必须或允许做什么的规范。编译器的行为是完全定义的,但它是由隐藏在解析器、代码生成器和优化器模块设计深处的详细决策定义的,而且它非常复杂,即使是编写编译器的开发人员也无法告诉你它会做什么而不花钱很多时间分析给定的代码如何贯穿整个过程。

初学者是猜不出结果的。即使是专业的开发人员也可能无法做到。这就是为什么 "undefined" 对开发人员来说是一个如此不受欢迎的词,以及为什么他们试图避免像瘟疫这样的未定义行为。引用有关语言规范的讨论,“简而言之,您不能在元素尚未被删除的结构上使用 sizeof() 定义,如果你这样做,恶魔可能会从你的鼻子里飞出来。".