C 什么';这就是为什么让a=a++;没有定义?

C 什么';这就是为什么让a=a++;没有定义?,c,undefined-behavior,language-lawyer,C,Undefined Behavior,Language Lawyer,在C中是未定义的行为。我要问的问题是:为什么? 我的意思是,我知道要提供一个一致的做事顺序可能很难。但是,某些编译器总是按照某种顺序(在给定的优化级别)进行编译。那么,为什么这完全要由编译器来决定呢 说得清楚一点,我想知道这是否是一个设计决策,如果是,是什么促使了它?或者可能存在某种硬件限制 (注意:如果问题标题不清楚或不够好,则欢迎反馈和/或更改)后缀++运算符返回递增之前的值。因此,在第一步,a被分配给它的旧值(这就是++返回的值)。在下一个点上,增量还是赋值将首先发生还没有定义,因为这两个

在C中是未定义的行为。我要问的问题是:为什么?

我的意思是,我知道要提供一个一致的做事顺序可能很难。但是,某些编译器总是按照某种顺序(在给定的优化级别)进行编译。那么,为什么这完全要由编译器来决定呢

说得清楚一点,我想知道这是否是一个设计决策,如果是,是什么促使了它?或者可能存在某种硬件限制


(注意:如果问题标题不清楚或不够好,则欢迎反馈和/或更改)

后缀
++
运算符返回递增之前的值。因此,在第一步,
a
被分配给它的旧值(这就是
++
返回的值)。在下一个点上,增量还是赋值将首先发生还没有定义,因为这两个操作都应用于同一个对象(
a
),并且语言没有说明这些操作符的求值顺序。

这是不明确的,但在语法上没有错。
a
应该是什么?
=
++
都有相同的“计时”。因此,没有定义任意顺序,而是保留了未定义的顺序,因为任意一个顺序都会与两个运算符定义中的一个相冲突。

有人可能会提供另一个原因,但这是出于优化(最好说是汇编程序演示)从角度看,
a
需要加载到CPU寄存器,后缀运算符的值应该放在另一个寄存器或相同的寄存器中。
因此,最后一个赋值可以取决于优化器使用一个寄存器或两个寄存器。

这是未定义的,因为没有很好的理由编写这样的代码,并且通过不要求伪代码有任何特定行为,编译器可以更积极地优化编写良好的代码。例如,
*p=i++
的优化方式可能会导致崩溃,如果
p
恰好指向
i
,可能是因为两个内核同时写入相同的内存位置。在特定情况下,
*p
被显式写为
i
,以获得
i=i++
,这一事实也恰好是未定义的,逻辑上如下所示。

在没有插入序列点的情况下更新同一对象两次是未定义的行为,因为

  • 因为这让编译器编写者更快乐
  • 因为它允许实现无论如何定义它
  • 因为它在不需要时不强制特定约束

除了少数例外,表达式的求值顺序未指定;这是一个经过深思熟虑的设计决策,它允许实现根据所编写的内容重新排列评估顺序,如果这样可以产生更高效的机器代码。类似地,应用
++
--
的副作用的顺序也未指定,超出了它在下一个序列点之前发生的要求,以使实现能够以最佳方式自由安排操作

不幸的是,这意味着像
a=a++
这样的表达式的结果将根据编译器、编译器设置、周围代码、,这种行为在语言标准中被明确称为未定义,因此编译器实现者不必担心检测到这种情况并对其进行诊断。像
a=a++
这样的例子很明显,但是像这样的例子呢

a = a++;
如果这是文件中唯一的函数(或者如果它的调用者在不同的文件中),那么在编译时无法知道
a
b
是否指向同一个对象;你是做什么的

请注意,完全可以强制所有表达式按特定顺序进行评估,并且所有副作用都应用于评估中的特定点;这就是Java和C所做的,在这些语言中,像
a=a++
这样的表达式总是定义良好的

更新:这个问题很重要。谢谢你的提问


为什么??我想知道这是否是一个设计决定,如果是,是什么促使它

你基本上是在索要ANSI C设计委员会的会议记录,而我手头没有这些。如果你的问题只能由那天在房间里的人来回答,那么你就必须找到那个房间里的人

然而,我可以回答一个更广泛的问题:

哪些因素导致语言设计委员会将法律程序的行为(
*
)“未定义”或“实现定义”(
***
)保留下来

第一个主要因素是:市场上是否有两种现有的语言实现对特定程序的行为存在分歧?如果FooCorp的编译器将
M(a(),B())
编译为“调用a,调用B,调用M”,而BarCorp的编译器将其编译为“调用B,调用a,调用M”,那么“显然是正确的”行为那么语言设计委员会就有强烈的动机说“你们都是对的”,并使其成为执行定义的行为。特别是如果FooCorp和BarCorp都有代表参加委员会的话

下一个主要因素是:该功能是否自然地提供了许多不同的实现可能性?例如,在C#中,编译器对“查询理解”表达式的分析被指定为“将语法转换为没有查询理解的等效程序,然后正常地分析该程序”,否则实现几乎没有自由

相比之下,C#规范指出,
foreach
循环应被视为
void foo(int *a, int *b)
{
  *a = (*b)++;
}
Func<int, int> f1 = (int x)=>x + 1;
Func<int, int> f2 = (int x)=>x + 1;
bool b = object.ReferenceEquals(f1, f2);