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

day*_*oli 70 c c++ int short integer-promotion

从我从得到的回答这个问题,看来C++继承了这一要求,对于转换shortint从C.执行算术运算时,我可以挑你的大脑,以为什么这是用C首先介绍?为什么不做这些操作short呢?

例如(取自评论中的dyp建议):

short s = 1, t = 2 ;
auto  x = s + t ;
Run Code Online (Sandbox Code Playgroud)

x将具有int类型.

Sha*_*our 39

如果我们在常用算术转换部分中查看国际标准编程语言-C的基本原理,那么(强调我的未来):6.3.1.8

这些转换的标准中的规则是对K&R中的规则的略微修改:修改适应添加的类型和保留值规则.添加显式许可证以在"更广泛"的类型中执行计算,而不是绝对必要的,因为这有时会产生更小更快的代码,更不用说更频繁的正确答案了.只要获得相同的最终结果,也可以通过as if规则以"较窄"类型执行计算.可以始终使用显式转换来获得所需类型的值

节6.3.1.8C99草案标准涵盖了通常的算术转换施加到算术表达式的操作数的例如部分6.5.6加法运算符表示:

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

我们在第6.5.5节" 乘法运算符"中也找到了类似的文本.在操作数的情况下,首先从6.3.1.1布尔,字符和整数中应用整数提升,其中:

如果int可以表示原始类型的所有值,则该值将转换为int; 否则,它将转换为unsigned int. 这些被称为整数促销.48)所有其他类型的整数提升不变.

关于整数促销6.3.1.1的基本原理或国际标准编程语言-C部分的讨论实际上更有趣,我将有选择地引用b/c它太长而无法完全引用:

实施属于两个主要阵营,其特点可能是无保留的保留和价值保留.

[...]

无符号保留的做法呼吁促进两个较小的无符号类型unsigned int类型.这是一个简单的规则,并产生一种独立于执行环境的类型.

值保存方法要求促进这些类型有符号整数,如果该类型可以正确表示原始类型的所有值,否则为促进这些类型unsigned int类型.因此,如果执行环境将short表示为小于int的东西,则unsigned short变为int; 否则它变成unsigned int.

在某些情况下,这会产生一些意想不到的结果,因为无符号和更大的有符号类型之间的隐式转换的不一致行为表明,还有更多这样的例子.虽然在大多数情况下,这会导致操作按预期工作.

  • 是的,有时它会更小更快,因为您不需要额外的指令来将值符号/零扩展到int或屏蔽高位.在x86中,您不需要额外的指令前缀来更改参数大小 (2认同)

Pho*_*non 22

它不是语言的一个特征,因为它是代码运行的物理处理器体系结构的限制.intC中的typer通常是标准CPU寄存器的大小.更多的硅占用更多空间和更多功率,因此在许多情况下,算术只能在"自然大小"数据类型上完成.这并非普遍适用,但大多数架构仍然存在此限制.换句话说,当添加两个8位数时,处理器中实际发生的是某种类型的32位算术,然后是简单的位掩码或其他适当的类型转换.

  • 我不同意"它不是语言的一个特征"; 它是该语言的一个特征.它的定义是因为......但它是由语言定义的,而不是由处理器定义的. (10认同)
  • 我不确定是否一定有点面具.处理器以其原始字大小执行算术,然后仅将较低位存储回存储器.(另外,虽然你认为大多数架构只进行单词算术是正确的,但一个值得注意的例外是英特尔,它的应用非常广泛.) (4认同)
  • @JonathanLeffler这肯定是该语言的一个特色.我想,在大多数语言中.但Phonon的回答解释了_why_语言有这个功能.(可能值得指出的是,在过去,机器只有字,而不是字节,半字等.当引入字节寻址时,它只影响存储器访问,而不影响寄存器和操作.所以尽管PDP-11有字节和字指令,当字节指令的目标地址是寄存器时,字节被符号扩展为一个字.) (2认同)
  • CPU如何执行命令对用户代码完全隐藏.你根本没有回答这个问题. (2认同)

650*_*502 18

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

只是

.L3:
    addb    $1, (%rdi,%rax)
    addq    $1, %rax
    cmpl    %eax, %esi
    jg  .L3
.L1:
Run Code Online (Sandbox Code Playgroud)

您可以在其中看到使用unsigned char addition指令(addb).

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


ssu*_*ube 8

链接的问题似乎很好地涵盖了它:CPU没有.32位CPU的本机算术运算设置为32位寄存器.处理器更喜欢以自己喜欢的尺寸工作,对于这样的操作,将一个小值复制到本机大小的寄存器是很便宜的.(对于x86架构,32位寄存器被命名为它们是16位寄存器的扩展版本(eaxto ax,ebxto bx等);请参阅x86整数指令).

对于一些非常常见的操作,特别是向量/浮点运算,可能存在对不同寄存器类型或大小进行操作的专用指令.对于类似短路的内容,使用(最多)16位零填充具有非常小的性能成本,并且添加专用指令可能不值得在芯片上花费时间或空间(如果您想真正了解原因;我是不确定他们会占用实际空间,但它确实变得更加复杂).

  • "请注意,32位寄存器也被命名为它们是16位寄存器的扩展版本(eax到ax,ebx到bx等)"对于x86来说这是正确的,但对于大多数其他**则不正确**架构.MIPS寄存器在32位或64位模式下具有相同的名称,并且它们始终以原始大小工作,因此无论如何都不能以8位或16位进行算术运算 (4认同)
  • 这不仅仅是一个硬件问题,在起草C99标准期间有一个有意识的选择,使整数促销工作以特定的方式工作. (2认同)