3

我一直试图围绕整数提升的 C99 规则和整数类型的常用算术转换。在烧掉几个神经元之后,我得出了一套我自己的规则,这些规则要简单得多,但我相信,它相当于官方的:

更新:为了这个问题,我首先定义“物理类型”如下

定义:如果两个整数类型具有相同的大小和符号,则它们是相同的物理类型。

如果你认为这个定义有问题,那么你可能对问题 2 有一个很好的答案。

简化的促销/转换规则

类型排名:在T1和T2两种积分类型中,“最好”的是:

  • 以较大者为准
  • 如果它们具有相同的大小,以未签名者为准
  • 如果它们具有相同的大小和符号,它们中的任何一个,因为它们在物理上是相同的。

积分提升:类型 T 的值应该提升为

提升(T) = 最佳(T, int)

整数类型的常用算术转换:在计算 T1 和 T2 类型的二元运算符之前,应将参数转换为合适的通用类型,即:

共同(T1,T2)=最佳(T1,T2,int)

警告:虽然我相信我的规则给出了正确的物理类型,但在单一类型具有不同名称的情况下,它们可能无法提供正确的类型名称。例如,在系统上int==long,官方规则说

common(unsigned int, long) = unsigned long

而我的规则说它是unsigned int(无论如何在物理上是相同的)。但这应该不是问题,因为名称并不重要。还是他们?

在这个漫长的前奏之后,真正的问题来了,它有两个方面:

问题1:我的规则正确吗?

我读了几遍官方的,但我仍然觉得它们令人困惑。因此,我可能误解了一些东西。如果我错了,请提供官方规则和我的规则产生不同类型的示例。我的意思是:不同的物理类型,而不仅仅是物理上相同的不同类型。

一个现实世界的例子将是首选。如果没有找到,如果假设的 C 环境的描述足够详细以令人信服(相关类型的大小等),那么理论上的示例就可以了。

如果我在这里是正确的,那么第二个问题就变得相关了。

问题 2:我为什么要关心提升/转换类型的名称?

如果我是正确的,那么显而易见的问题是“标准委员会的人为什么要编写如此复杂的规则?”。他们唯一的回答是我能想到的,他们不仅要指定促销/转换产生的物理类型,还要指定命名这些类型的正确方法。但是,他们为什么在乎呢?这些类型仅由编译器在内部使用,只要我们了解它们的物理性质,我们如何命名它们并不重要。这个推理有问题吗?您能想到这样一种情况,即 T1 和 T2 在物理上是相同的,但重要的是要知道事物是否会自动升级或转换为 T1 而不是 T2?同样,现实世界的示例将是首选,否则,如果足够详细,理论示例也可以。

基本原理

(部分添加于 2014-11-10,以解决一些评论)

在搜索这些主题时,我总是看到它们在算术运算符行为的上下文中进行讨论,更具体地说,是这些运算符返回的结果。例如,表达式-1L < 1U是有问题的,因为它在 long 确实比 int 长的系统上为 true,否则为 false。我相信理解这类问题是一件好事,但需要一个复杂的规则集来做到这一点是一件坏事。因此,努力构建一个更简单的规则集,可靠地给出相同的结果。

我完全明白,我的规则对于任何发现真实规则足够简单的人来说都是无用的。我也理解并且恭敬地不同意那些表示依赖官方规则以外的任何东西本质上是不好的观点的人。不过,如果我的规则对除我以外的任何人都有帮助的话,它们还是会有用处的。

关于我个人的偏见:作为一名物理学家,我非常看重简单性。我习惯于处理那些不是——也不应该是——终极真理的理论,但它们被证明是非常有用的,只要你了解它们的适用范围,它们就可以安全使用。在任何给定的情况下,最好的理论都不是最完整的:它是仍然适用的最简单的理论。例如:我不会使用量子引力来计算单摆的周期。我在这里发布这个问题是为了就上述规则的适用范围获得专家意见。

到目前为止,我所拥有的是:

  • varargs 案例(坦克,mafso),这似乎是 C99 中这些规则至少在原则上不适用的唯一情况
  • 关键字(感谢 Pascal Cuoq) ,_Generic作为 C11 功能,稍微超出范围
  • C++11auto关键字,它进一步超出了范围,但有趣的是它会带来(否则不相关的)关于别名规则的担忧。
4

2 回答 2

2

关于您的第一个问题,我认为答案是“是”:在所有正常或什至有点异国情调的平台上,您提出的规则会产生与标准规则相同的表示类型。

关于你的第二个问题,这里有两种类型的“名称”很重要的情况(为了清楚起见,我只是使用你的术语;在标准的措辞中,即使它们恰好大小相同,也是不兼容的类型longint

C11 的_Generic 构造:即使两者都是整数的 32 位表示,long表达式也不匹配大小写。int

严格的别名:允许编译器生成假设变量在您修改左值int时不会改变的代码。long特别是,下面代码中的语句 1 和 3 可以优化为return 1;

{
  long *p;
  int x;
  …
  x = 1; /* 1 */
  *p = 2;
  return x; /* 3 */
}

顺便说一句,该标准不允许printf("%d", 1L)printf("%ld", 1)即使两者大小相同,尽管它会在大多数平台上运行(我不将此作为重要示例,因为颁布它不会对标准进行重大更改当类型具有相同的表示时应该可以工作,这与上面的两个示例不同)。

于 2014-11-08T18:39:58.017 回答
1

由于各种平台在正式标准化 C 之前就开始使用 C 作为语言,因此各种不寻常平台的 C 编译器以各种有趣的方式实现事物。该标准的作者并没有强制此类平台的编译器使用与现有代码不兼容的新规则,而是尝试编写许多皱纹和细微差别以适应所有这些奇怪架构的行为。

在实践中,在大多数平台上,一个大大简化的规则版本都可以应付。没有办法在编译时断言具有相同范围的类型的等价性(例如,我不知道标准中需要 32-bitunsigned int值使用与 32-bit 相同的字节顺序unsigned long)和指针的类型即使它们指向的类型在物理上是相同的,也必须将不同的类型视为语义上不同的,但正如其他地方所指出的那样,编译器被允许具有类型和右值的唯一情况unsigned intunsigned long当两者具有相同的表示时,行为会有所不同,当这些类型作为可变参数传递时,这种情况比应该的要混乱得多,并导致long double类型的垮台。

如果可变参数函数的原型可以指定“将所有整数值转换为 X,所有浮点值转换为 Y 类型”,并且“vararg.h”和“stdarg.h”仍然作为不同的特征(前者用于没有此类原型的函数,后者用于具有它们的函数),这将避免担心 anint32_t是 anint还是 a 的需要long。避免由于缺少该功能而导致的问题为时已晚,尽管仍然值得添加以缓解未来的问题。

也许有一天有人会分离出一种单独的“规范 C”语言,该语言使用简化的规则,在现代平台上产生与 C 相同的行为,但如果它们符合现代用法(例如具有符号/幅度硬件的机器),则只能在晦涩的平台上实现将需要计算i+j(int)(((i ^ 0x80000000u)+j)^0x80000000u)),在这种情况下,可以省略仅适用于古怪机器的标准方面。

于 2015-05-13T19:37:06.257 回答