read()从array[0]读取返回array[1]中的值吗?

26 浏览
0 Comments

read()从array[0]读取返回array[1]中的值吗?

“未定义行为”的经典虚构例子当然是“鼻妖”,这是一种物理上不可能的情况,无论C和C++标准允许什么。

因为C和C++社区往往非常强调未定义行为的不可预测性,以及编译器在遇到未定义行为时允许程序做任何事情的观念,我原以为标准对于未定义行为的行为没有任何限制。

但是C++标准中的相关引用似乎是:

[C++14: defns.undefined]: [...] 允许的未定义行为范围从完全忽略情况并产生不可预测的结果,到在翻译或程序执行期间表现出环境特征的一种已记录方式(无论是否发出诊断消息),到终止翻译或执行(附带发出诊断消息)。[...]

这实际上指定了一小组可能的选项:

- 忽略情况 - 是的,标准继续说这将产生“不可预测的结果”,但这与编译器插入代码(我认为这是产生鼻妖的先决条件)是不同的。

- 表现出环境特征的一种已记录方式 - 这听起来相对无害。(我当然没有听说过任何关于鼻妖的已记录案例。)

- 终止翻译或执行 - 还附带诊断消息。如果所有未定义行为都能如此友好地表现就好了。

我认为在大多数情况下,编译器选择忽略未定义行为;例如,在读取未初始化的内存时,插入任何代码以确保一致的行为可能是一种反优化。我猜测较奇怪的未定义行为类型(比如“时间旅行”)会属于第二类 - 但这需要这些行为被记录并且“具有环境特征”(所以我猜鼻妖只能由地狱般的计算机产生?)。

我是否误解了定义?这些是否只是可以构成未定义行为的示例,而不是一个全面的选项列表?声称“任何事情都可能发生”是否只是忽略情况的意外副作用?

两个小的澄清点:

- 我认为从原始问题中很明显,对大多数人来说也是如此,但我还是要明确说明一下:我确实意识到“鼻妖”是半开玩笑的。

- 请不要写一个解释未定义行为允许特定平台编译器优化的答案,除非你还解释了它如何允许实现定义行为不允许的优化。


这个问题并不打算讨论未定义行为的优缺点,但这似乎就是它变成的样子。无论如何,对于那些认为这是一个重要话题的人来说,这个关于假设没有未定义行为的C编译器的讨论线程可能会更有兴趣。

0
0 Comments

历史上,Undefined Behavior的一个目的是允许某些操作在不同平台上具有不同的潜在有用效果。例如,在C的早期阶段,给定

int i = INT_MAX;
i++;
printf("%d",i);

一些编译器可以保证代码将打印出某个特定值(对于一个二进制补码机器来说,通常是INT_MIN),而其他编译器可以保证程序在到达printf之前终止。根据应用程序的要求,任何一种行为都可能是有用的。将行为定义为未定义意味着应用程序可以根据平台的不同来决定是否进行溢出检查。

然而,最近一些编译器作者似乎陷入了一个比赛,看看谁能最高效地消除任何不被标准规定强制存在的代码。例如...

#include <stdio.h>

int main(void)

{

int ch = getchar();

if (ch < 74)

printf("Hey there!");

else

printf("%d",ch*ch*ch*ch*ch);

}

[/apcode]

一个超现代的编译器可能会得出结论,如果ch大于等于74,则ch*ch*ch*ch*ch的计算将产生Undefined Behavior,并且结果程序应该无条件地打印"Hey there!",而不管输入了什么字符。

哇。你知道我们是如何从"潜在有用"到当前的情况的吗?C++社区中的许多人似乎坚决反对任何试图确定编译器在遇到允许UB的情况时的确切行为的尝试,并解释说"这没有关系,你的程序有UB"?

这一切都是关于基准测试

不,这是关于可移植性。我们现在生活在一个互联互通的时代,软件的分发速度比你想象的还要快。我们不再只为地下室里那台古老的超级计算机编写程序。至少,我们中的大多数人不是。这实际上是一个编程中几十年来的范式转变;现在严格按照标准进行编码有了实际的实用优势(理想情况下我们一直都这样做),工具链的编写者可以利用这一点来生成非常快速和高效的编译器。为什么不呢?

:你能写一个严格符合规范的函数,当a*bc*d的值可以表示为int时,它的行为与int mulcomp(int a, int b, int c, int d) { return a*b > c*d;}相同,并且当a*bc*d的值不能表示为int时,它必须返回1,返回0或终止执行(任意选择),而且该函数的可读性不比原始函数差,并且在至少一些平台上,"可移植"代码的最佳代码与原始代码相比并不显著地更慢吗?

:如果目标是有一个可用的可移植语言,委员会应该承认一些不同的变体的存在(例如,在某些方言中,p >= object.base && p<object.base+object.size)可以用来测试p是否是对象的一部分,但在所有平台上都无法实现,而那些不允许这样的比较但在更多平台上可以实现的方言)。它还应该定义一些数据类型,如果支持,就必须在所有平台上表现一致。目前,C有两种不同的32位有符号整数类型...

...以及两种不同的无符号32位整数类型。在所有uint32_t值都可以表示为int的平台上,两个uint32_t值相减将产生一个有符号的结果。在一些uint32_t值不能表示为int的平台上,相减将产生一个uint32_t结果。两种类型都被称为uint32_t,但它们的语义非常不同。同样,在int大于32位的平台上,对int32_t进行递增总是有定义行为的。在int正好是32位的平台上,对int32_t进行递增可能会导致UB。

:此外,一个可移植的语言应该定义一种高效的可移植手段来将较大的整数类型打包和解包成/从一系列较小的类型。写*dat++= value & 255; *dat++=(value >> 8) & 255; *dat++ = (value >> 16) & 255; *dat++ = (value >> 24) & 255;可能是100%可移植的(即使对于CHAR_BITS > 8的机器),但即使在一个单独的32位存储器可以产生正确行为的平台上,编译器也很难确定。给定__pack_i32_cle(&dat, value);,任何编译器都可以轻松生成最优代码。

我认为很难为UB的存在辩解;它的缺点多于优点

:有一种行为类别的存在有很大的益处,其中不同的平台可能能够提供不同的行为保证,这些保证可能不一定兼容(例如,有些可能保证将1添加到MAX_NT会触发陷阱,有些可能保证它会产生MIN_INT,有些可能保证它会产生一些数值,不一定在int范围内,它与(MAX_INT+1)mod(MAX_INT+MAX_INT+2等)相合)。相反的哲学是,一个被告知"你可以假设有人会替你清理"的人...

...如果事实证明没有人确实为他清理,他将有权做任何他想做的事情。

有数百种不具有UB的语言,也许可以使用其中之一,而不是试图使所有语言都一样

:说没有任何行为应该有未定义行为,与说许多在C中具有完全不受约束行为的行为不应该有未定义行为是有区别的。优化机会最大化的方法是,编译器提供尽可能宽松的行为保证,但又不增加程序员编写满足要求所需的代码的数量。不提供这样的保证将迫使程序员编写更冗长、更难读、更慢、更不利于优化的代码,而这些代码本来是可能的。

但很多人(包括我自己)出于必要使用C或C++;更多人(实际上很可能是每个使用现代计算机的人)使用用C和C++编写的程序,我怀疑每个人都熟悉的许多神秘的错误/错误/崩溃等都是由于UB引起的。

0
0 Comments

从下面的内容中可以整理出(read() from array[0] returns value in array[1]?)这个问题的出现的原因以及解决方法。

问题的出现原因是因为在ISO/IEC Directives Part 2中明确规定,注释和示例不是规范性文件。注释和示例只能用于提供额外的信息来帮助理解或使用文档,并且不应包含规范要求或对文档使用不可或缺的信息。这意味着注释和示例不能作为文档的规范要求或基本信息的一部分。然而,提供示例可以被认为是为了帮助理解文档的额外信息。

针对这个问题的解决方法是要注意"nasal demon"这个梗不是字面上的意思,就像用气球解释宇宙膨胀如何工作并不是物理现实的真理。这只是为了说明在允许执行任何操作的情况下,讨论"未定义行为"应该做什么是愚蠢的。这意味着在外层空间中并没有实际的橡皮筋。

另外,C标准使用的"shall"一词与其他标准的用法有所不同。在大多数标准中,违反约束会使实现非符合性,但C标准并不是如此。违反约束的程序不能被严格认为是符合性的,但标准承认这些非便携程序是"符合性"的,并明确表示不会贬低对其不提出要求但行为由某些实现有用地定义的非便携程序。

总结起来,问题的出现原因是使用了不是规范性文件的注释和示例来解释C++标准。解决方法是遵循ISO/IEC Directives Part 2的规定,注释和示例只能用于提供额外的信息,不能包含规范要求或基本信息。此外,需要注意不要将"nasal demon"这个梗理解为字面意思,而是将其作为一个比喻来理解。另外,C标准中的"shall"一词与其他标准的用法有所不同,违反约束并不会使实现非符合性。

0