为啥在 C 和 C++ 中的算术运算之前必须将 short 转换为 int?

Posted

技术标签:

【中文标题】为啥在 C 和 C++ 中的算术运算之前必须将 short 转换为 int?【英文标题】:Why must a short be converted to an int before arithmetic operations in C and C++?为什么在 C 和 C++ 中的算术运算之前必须将 short 转换为 int? 【发布时间】:2014-06-23 17:30:34 【问题描述】:

根据我从this question 得到的答案,似乎 C++ 在从 C 中执行算术运算时继承了将short 转换为int 的要求。我想请你动脑筋为什么首先在 C 中引入?为什么不直接以short 进行这些操作?

例如(取自 dyp 在 cmets 中的建议):

short s = 1, t = 2 ;
auto  x = s + t ;

x 的类型为 int

【问题讨论】:

@Jefffrey 积分提升是通常算术转换的一部分。 short s=1, t=2; auto x = s+t; 然后xint maxshort + maxshort > maxshort @technosaurus 无法解释为什么 int 没有提升为 long (maxint + maxint > maxint)。 我没有得到这个问题的反对意见。这是一个很好的问题,答案很有趣。四次投反对票而没有 cmets 非常令人沮丧。 @dyp:为什么xint 类型的规则在 C 和 C++ 中完全不同... ;-) 【参考方案1】:

如果我们查看6.3.1.8 部分中的Rationale for International Standard—Programming Languages—C 通常的算术转换,它会说(强调我的未来):

标准中关于这些转换的规则是轻微的 K&R 中的修改:修改适应添加的 类型和值保留规则。 显式许可证已添加到 以比绝对必要的“更广泛”的类型执行计算, 因为这有时会产生更小更快的代码,而不是 更频繁地提及正确答案。计算也可以 只要相同 得到最终结果。显式转换总是可以用来获得 所需类型的值

来自draft C99 standard 的

Section 6.3.1.8 涵盖了应用于算术表达式操作数的常用算术转换,例如6.5.6 Additive operators 部分说:

如果两个操作数都有算术类型,通常的算术 对它们执行转换

我们在6.5.5 Multiplicative operators 部分也发现了类似的文字。对于 short 操作数,首先从6.3.1.1 Boolean, characters, and integers 部分应用 integer Promotion,该部分表示:

如果一个 int 可以表示原始类型的所有值,则该值为 转换为 int;否则,它将转换为无符号整数。 这些被称为整数促销48)所有其他类型都是 整数促销保持不变。

Rationale or International Standard-Programming Languages-C6.3.1.1 部分关于整数提升 的讨论实际上更有趣,我将选择性地引用 b /c 太长,无法完整引用:

实施分为两大阵营 作为无符号保留和价值保留

[...]

无符号保留方法要求促进两个较小的 无符号类型为无符号整数。这是一个简单的规则,并产生一个 与执行环境无关的类型。

保值方法要求将这些类型推广到 如果该类型可以正确表示 原始类型,否则用于将这些类型提升为无符号 诠释。因此,如果执行环境将 short 表示为某物 小于 int,unsigned short 变为 int;否则它变成 无符号整数。

正如Inconsistent behaviour of implicit conversion between unsigned and bigger signed types 所展示的那样,在某些情况下,这可能会产生一些相当出乎意料的结果,这样的例子还有很多。尽管在大多数情况下,这会导致操作按预期进行。

【讨论】:

是的,有时它会更小更快,因为您不需要额外的指令来将值符号/零扩展为 int 或屏蔽高位。在 x86 中,您也不需要额外的指令前缀来更改参数大小 太糟糕了,基本原理没有添加第二条规则,如果加法、乘法或按位运算符的结果被强制为小于 int 的无符号类型,则表达式的行为就像它的操作数同样被强制,并且操作在较小的类型上执行。没有定义的情况会与这样的规则相矛盾,但是一些编译器可能会以提升为借口来推断像 x*=y; 这样的语句(带有两个变量 unsigned short)承诺 x 不能超过 2147483648/y。 如果我有类似 int x = 1234char *y = &x 的东西。 1234 的二进制表示是 00000000 00000000 00000100 11010010 。我的机器是小端的,所以它将它反转并存储在内存中11010010 00000100 00000000 00000000 LSB 是第一位的。现在主要部分。如果我使用printf("%d" , *p)printf 将读取第一个字节 11010010only 输出是 -4611010010210 那么为什么它打印 -46 。我真的很困惑,我猜一些字符到整数的提升正在做一些事情,但我不知道。 您引用了 C99 标准,但这种行为不早于此吗?我要睡觉了,我去看看能不能在 K&R 找到什么。 @PJTrailll 好吧wikipedia 指向c89 的一个版本,尽管您无法获得正式的草稿。在那个版本的通常的算术转换下,它描述了一个非常相似的过程。所以我会说是的。请注意上面的引述对 K&R 中的内容进行了轻微修改,因此 K&R 应该有所不同。【参考方案2】:

这与其说是语言的特性,不如说是对运行代码的物理处理器架构的限制。 C 语言中的 int 类型通常是标准 CPU 寄存器的大小。更多硅片占用更多空间和更多功率,因此在许多情况下只能对“自然大小”数据类型进行算术运算。这并非普遍正确,但大多数架构仍然存在此限制。换句话说,当添加两个 8 位数字时,处理器中实际发生的是某种类型的 32 位算术,然后是简单的位掩码或其他适当的类型转换。

【讨论】:

我不确定是否有位掩码。处理器以其本机字长进行算术运算,然后仅将低位存储回内存。 (此外,虽然您说得对,大多数架构只做字算术,但一个值得注意的例外,英特尔,分布非常广泛。) @JamesKanze 你是对的。我按答案编辑。是的,英特尔在优化算法方面遥遥领先,尤其是使用 IPP 库。 我不同意“这不是语言的特征”;它语言的一个特征。它是这样定义的,因为......但它是由语言定义的,而不是由处理器定义的。 @JonathanLeffler 这当然是该语言的一个特性。在大多数语言中,我认为。但 Phonon 的回答解释了 为什么 语言具有此功能。 (可能值得指出的是,在过去,机器只有字,而不是字节、半字等。当引入字节寻址时,它只影响内存访问,而不影响寄存器和操作。所以虽然 PDP-11 有字节指令和字指令,当字节指令的目标地址是寄存器时,字节被符号扩展为字。) CPU 执行命令的方式对用户代码完全隐藏。你根本没有回答这个问题。【参考方案3】:

shortchar 类型被标准类型的“存储类型”考虑,即您可以用来节省一些空间但不会为您带来任何速度的子范围,因为它们的大小是“不自然的” " 代表 CPU。

在某些 CPU 上这是不正确的,但是好的编译器足够聪明,可以注意到,如果你例如将常量添加到 unsigned char 并将结果存储回 unsigned char 中,则无需进行 unsigned char -> int 转换。 例如用g++生成的内循环代码

void incbuf(unsigned char *buf, int size) 
    for (int i=0; i<size; i++) 
        buf[i] = buf[i] + 1;
    

只是

.L3:
    addb    $1, (%rdi,%rax)
    addq    $1, %rax
    cmpl    %eax, %esi
    jg  .L3
.L1:

您可以看到使用了无符号字符加法指令 (addb)。

如果您在短整数之间进行计算并将结果存储在短整数中,也会发生同样的情况。

【讨论】:

【参考方案4】:

链接的问题似乎很好地涵盖了它:CPU 只是没有。 32 位 CPU 为 32 位寄存器设置了本机算术运算。处理器更喜欢以它最喜欢的大小工作,对于这样的操作,将一个小值复制到本机大小的寄存器是很便宜的。 (对于 x86 架构,32 位寄存器被命名为好像它们是 16 位寄存器的扩展版本(eaxaxebxbx 等);参见 x86 integer instructions) .

对于一些极其常见的操作,尤其是向量/浮点算术,可能会有专门的指令对不同的寄存器类型或大小进行操作。对于短的东西,用(最多)16位零填充几乎没有性能成本,并且添加专门的指令可能不值得在模具上花费时间或空间(如果你想真正了解原因;我不确定它们是否会占用实际空间,但它确实变得更加复杂)。

【讨论】:

这不仅仅是一个硬件问题,在起草 C99 标准的过程中做出了一个有意识的选择,以使整数提升以一种特定的方式工作。 “请注意,32 位寄存器也被命名为好像它们是 16 位寄存器的扩展版本(eax 到 ax,ebx 到 bx 等)”这对于 x86 是正确的,但是 不正确对于大多数其他架构。无论在 32 位还是 64 位模式下,MIPS 寄存器都具有相同的名称,并且它们始终以本机大小工作,因此无论如何您都不能在 8 位或 16 位中进行算术运算

以上是关于为啥在 C 和 C++ 中的算术运算之前必须将 short 转换为 int?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 指针警告:算术溢出:对 4 字节值使用运算符“-”,然后将结果转换为 8 字节值

没有条件、循环和算术运算符的 C 中的阶乘

将基本算术运算符存储在变量中

为啥不允许将数组按值传递给 C 和 C++ 中的函数?

从C ++代码中提取算术运算信息[关闭]

为啥算术赋值运算符更有效? [复制]