为啥在 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;
然后x
是int
。
maxshort + maxshort > maxshort
@technosaurus 无法解释为什么 int
没有提升为 long
(maxint + maxint > maxint)。
我没有得到这个问题的反对意见。这是一个很好的问题,答案很有趣。四次投反对票而没有 cmets 非常令人沮丧。
@dyp:为什么x
是int
类型的规则在 C 和 C++ 中完全不同... ;-)
【参考方案1】:
如果我们查看6.3.1.8
部分中的Rationale for International Standard—Programming Languages—C 通常的算术转换,它会说(强调我的未来):
来自draft C99 standard 的标准中关于这些转换的规则是轻微的 K&R 中的修改:修改适应添加的 类型和值保留规则。 显式许可证已添加到 以比绝对必要的“更广泛”的类型执行计算, 因为这有时会产生更小更快的代码,而不是 更频繁地提及正确答案。计算也可以 只要相同 得到最终结果。显式转换总是可以用来获得 所需类型的值
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-C 的6.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 = 1234
和 char *y = &x
的东西。 1234
的二进制表示是 00000000 00000000 00000100 11010010
。我的机器是小端的,所以它将它反转并存储在内存中11010010 00000100 00000000 00000000
LSB 是第一位的。现在主要部分。如果我使用printf("%d" , *p)
。 printf
将读取第一个字节 11010010
only 输出是 -46
但 11010010
是 210
那么为什么它打印 -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】:short
和 char
类型被标准类型的“存储类型”考虑,即您可以用来节省一些空间但不会为您带来任何速度的子范围,因为它们的大小是“不自然的” " 代表 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 位寄存器的扩展版本(eax
到 ax
、ebx
到 bx
等);参见 x86 integer instructions) .
对于一些极其常见的操作,尤其是向量/浮点算术,可能会有专门的指令对不同的寄存器类型或大小进行操作。对于短的东西,用(最多)16位零填充几乎没有性能成本,并且添加专门的指令可能不值得在模具上花费时间或空间(如果你想真正了解原因;我不确定它们是否会占用实际空间,但它确实变得更加复杂)。
【讨论】:
这不仅仅是一个硬件问题,在起草 C99 标准的过程中做出了一个有意识的选择,以使整数提升以一种特定的方式工作。 “请注意,32 位寄存器也被命名为好像它们是 16 位寄存器的扩展版本(eax 到 ax,ebx 到 bx 等)”这对于 x86 是正确的,但是 不正确对于大多数其他架构。无论在 32 位还是 64 位模式下,MIPS 寄存器都具有相同的名称,并且它们始终以本机大小工作,因此无论如何您都不能在 8 位或 16 位中进行算术运算以上是关于为啥在 C 和 C++ 中的算术运算之前必须将 short 转换为 int?的主要内容,如果未能解决你的问题,请参考以下文章