gpt4 book ai didi

c - 让a = a++的语义未定义的原因是什么?

转载 作者:行者123 更新时间:2023-12-01 23:34:06 24 4
gpt4 key购买 nike

a = a++;

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

我的意思是,我知道很难以统一的顺序完成工作。但是,某些编译器将始终按一种顺序或另一种顺序(在给定的优化级别)执行该操作。那么,为什么要由编译器来决定呢?

明确地说,我想知道这是否是设计决定,如果是,是什么促使它做出的?也许存在某种硬件限制?

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

最佳答案

更新:这个问题是the subject of my blog on June 18th, 2012。感谢您提出的好问题!

Why? I want to know if this was a design decision and if so, what prompted it?



您实质上是在询问ANSI C设计委员会的 session 记录,而我没有这些。如果您的问题只有当天在房间里的某个人才能明确回答,那么您将必须找到在那个房间里的某个人。

但是,我可以回答一个更广泛的问题:

What are some of the factors that lead a language design committee to leave the behaviour of a legal program (*) "undefined" or "implementation defined" (**)?



第一个主要因素是: 市场上是否存在两种现有的语言实现,它们与特定程序的行为不同? 如果FooCorp的编译器将 M(A(), B())编译为“ call A, call B, call M”,而BarCorp的编译器将其编译为“ call B, call A, call M”,并且都不是“明显正确”的行为,那么就有强烈的动机去语言设计委员会说“你们都是对的”,并使其实现定义的行为。如果FooCorp和BarCorp均在委员会中有代表,则情况尤其如此。

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

相比之下,C#规范指出 foreach循环应被视为 while块内的等效 try循环,但允许实现一些灵活性。允许C#编译器说“例如,我知道如何在数组上更有效地实现 foreach循环语义”,并使用数组的索引功能,而不是按照规范建议的那样将数组转换为序列。

第三个因素是: 功能是如此复杂,以至于很难对其详细行为进行详细分类或指定? C#规范确实很少说明如何实现匿名方法,lambda表达式,表达式树,动态调用,迭代器块和异步块。它仅描述所需的语义和行为上的一些限制,其余部分留待实现。

第四个因素是: 该功能会给编译器分析带来很大负担吗? 例如,在C#中,如果您具有:
Func<int, int> f1 = (int x)=>x + 1;
Func<int, int> f2 = (int x)=>x + 1;
bool b = object.ReferenceEquals(f1, f2);

假设我们要求b为真。您将如何确定两个函数何时“相同”?做“内涵”分析-功能体具有相同的内容吗? -很难,并且要进行“扩展性”分析-如果输入相同,函数的结果是否相同? -更加困难。语言规范委员会应设法减少实现团队必须解决的公开研究问题的数量!

因此,在C#中,这留待实现定义。编译器可以自行决定选择使它们引用相等或不同。

第五个因素是: 该功能是否对运行时环境造成了沉重负担?

例如,在C#中,对数组末尾的解引用是明确定义的;它产生一个数组索引超出范围的异常。可以在运行时以很小的成本(不是零,而是很小的成本)实现此功能。用空接收器调用实例或虚拟方法被定义为产生一个空被引用的异常。再次,这可以以小的但非零的成本实现。消除不确定行为的好处是为运行时间付出了很小的代价。

第六个因素是: 是否会使行为定义为排除某些主要的优化?例如,当从导致副作用的线程进行观察时,C#定义了副作用的顺序。但是,观察到一个线程来自另一个线程的副作用的程序的行为是实现定义的,除了一些“特殊”副作用。 (就像 Volatile 的写操作或输入锁一样。)如果C#语言要求所有线程以相同的顺序观察到相同的副作用,那么我们将不得不限制现代处理器有效地完成其工作。现代处理器依靠乱序执行和复杂的缓存策略来获得高性能。

这些只是想到的几个因素。当然,在设计功能“实现定义”或“未定义”之前,语言设计委员会会讨论许多其他因素。

现在让我们回到您的特定示例。

C#语言确实使该行为得到严格定义( );观察到增量的副作用发生在分配的副作用之前。因此,那里不可能有任何“好吧,这根本不可能”的论点,因为可以选择一种行为并坚持下去。这也不排除优化的主要机会。而且没有多种可能的复杂实现策略。

因此,我的猜测是,我强调这是一个猜测,是C语言委员会将副作用排序为实现定义的行为,因为市场上有多个编译器以不同的方式进行操作,显然没有一个“更正确”,委员会不愿意告诉其中一半他们错了。

( *)或有时是其编译器!但是,让我们忽略这个因素。

( **)“未定义”行为表示该代码可以执行任何操作,包括擦除硬盘。不需要编译器生成具有任何特定行为的代码,也不需要告诉编译器它正在生成具有未定义行为的代码。 “定义为实现”的行为意味着,编译器作者在选择实现策略时具有相当大的自由度,但是需要选择一种策略,并始终如一地使用它并记录该选择。

( )当然,从单个线程观察时。

关于c - 让a = a++的语义未定义的原因是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9943697/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com