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


74

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

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

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

x将具有int类型。


7
@Jefffrey积分提升是常规算术转换的一部分。short s=1, t=2; auto x = s+t;然后x是一个int
dyp 2014年

3
maxshort + maxshort> maxshort
technosaurus

23
@technosaurus不能解释为什么int不提升为long(maxint + maxint> maxint)。
2014年

10
我对这个问题不赞成。这是一个很好的问题,答案很有趣。四票不赞成投票,没有任何意见可言。
Shafik Yaghmour 2014年

1
@dyp:尽管如此,为什么x类型的规则int在C和C ++中却完全不同... ;-)
Deduplicator

Answers:


42

如果我们看一下国际标准理由-编程语言-C6.3.1.8 通常的算术转换”部分中,它会说(强调我的前进):

这些转换的标准中的规则是对K&R中的那些规则的略微修改:这些修改包含添加的类型和值保留规则。添加了显式许可证,以比绝对必要的“更广泛”的类型执行计算,因为这有时会产生更小和更快的代码,更不用说正确答案了。只要获得相同的最终结果,也可以按照规则使用“更窄”的类型执行计算。显式强制转换始终可以用于获取所需类型的值

第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。

在某些情况下,这可能会产生一些出乎意料的结果,因为无符号和较大的带符号类型之间隐式转换的不一致行为表明,还有更多类似的示例。尽管在大多数情况下这会导致操作按预期进行。


2
是的,有时它会变得更小,更快,因为您不需要额外的指令来对值进行符号签名/置零,将值扩展为int或屏蔽高位。在x86中,您也不需要额外的指令前缀即可更改参数大小
phuclv 2014年

太糟糕了,理由是没有添加一个次要规则,即如果将加,乘或按位运算符的结果强制转换为小于的无符号类型int,则该表达式的行为就像其操作数也被强制转换并且对较小的类型。没有确定的情况会与该规则相抵触,但是某些编译器可能会以提升为借口来推断像这样的语句x*=y;(带有两个变量unsigned short)承诺x不能超过2147483648 / y。
超级猫

如果我有这样的事情 int x = 1234char *y = &x1234 is的二进制表示形式00000000 00000000 00000100 11010010。我的机器是小字节序的,因此它将其反转并存储在内存中11010010 00000100 00000000 00000000LSB首先出现。现在主要部分。如果我使用printf("%d" , *p)printf将读取第一个字节11010010只输出-46,但11010010就是210为何还打印-46。我真的很困惑,我想一些char到整型促销正在做一些事情,但我不知道。
Suraj Jain

您引用了C99标准,但是这种行为是否早于此?我需要上床睡觉,看看我能不能在K&R找到东西。
PJTraill '17

@PJTraill很好的维基百科指向c89的版本,尽管您无法获得正式草案。在通常的算术转换下的该版本中,它描述了非常相似的过程。所以我会说是的。请注意,上面的引言对K&R中的内容进行了细微修改,因此K&R应该有所不同。
Shafik Yaghmour

22

它不是语言的功能,而只是对运行代码的物理处理器体系结构的限制。intC语言中的打字机通常是标准CPU寄存器的大小。更多的硅占用更多的空间和更多的功率,因此在许多情况下,只能对“自然大小”数据类型进行算术运算。并非普遍如此,但是大多数体系结构仍然有此限制。换句话说,当将两个8位数字相加时,处理器中实际发生的是某种类型的32位算术运算,然后是简单的位掩码或另一种适当的类型转换。


4
我不确定是否一定有遮罩。处理器以其本机字大小执行算术运算,然后仅将低位存储回内存。(另外,虽然您说对了,大多数架构只做单词算术,但英特尔是一个非常明显的例外,分布非常广泛。)
James Kanze 2014年

@JamesKanze你是对的。我按答案编辑。是的,在优化算法方面,尤其是使用其IPP库时,英特尔是领先的。
声子

11
我不同意“这不是语言的功能”;这语言的功能。之所以这样定义,是因为...但它是由语言而不是处理器定义的。
Jonathan Leffler 2014年

2
@JonathanLeffler这肯定是语言的功能。我认为在大多数语言中。但是声子的答案解释了为什么语言具有此功能。(可能值得指出的是,在过去,机器只有字,没有字节,半字等。当引入字节寻址时,它仅影响内存访问,而不影响寄存器和操作。因此,PDP-11拥有字节和字指令,当字节指令的目标地址是寄存器时,该字节被符号扩展为一个字。)
James Kanze 2014年

2
用户代码完全隐藏了CPU执行命令的方式。您根本没有回答问题。
苏菲特2014年

18

shortchar标准类型的“存储类型”考虑了类型和类型,即可以用于节省空间的子范围,但这些子范围不会给您带来任何速度,因为它们的大小对于CPU而言是“不自然的”。

在某些CPU上,这是不正确的,但是好的编译器足够聪明,可以注意到,例如,如果您将一个常量添加到一个未签名的char中,然后将结果存储回一个未签名的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)。

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


8

链接的问题似乎很好地涵盖了这一点:CPU却没有。32位CPU为32位寄存器设置了其本机算术运算。处理器更喜欢以自己喜欢的大小工作,对于这样的操作,将较小的值复制到本机大小的寄存器中很便宜。(对于x86体系结构,将32位寄存器命名为好像它们是16位寄存器的扩展版本(eaxto axebxtobx等等);请参见x86整数指令)。

对于某些极其常见的操作,尤其是矢量/浮点运算,可能会有专门的指令对不同的寄存器类型或大小进行操作。对于较短的内容,(最多)16位零填充几乎没有性能成本,添加专用指令可能不值得在芯片上花费时间或空间(如果您想真正了解原因,我是不确定他们会占用实际空间,但确实会变得更复杂)。


2
这不仅仅是一个硬件问题,在起草C99标准的过程中做出了明智的选择,使整数促销以一种特定的方式进行。
Shafik Yaghmour 2014年

4
“请注意,还对32位寄存器进行了命名,就像它们是16位寄存器的扩展版本一样(从eax到ax,从ebx到bx等)”,对于x86是正确的,但在大多数其他体系结构中却不正确。无论在32位还是64位模式下,MIPS寄存器都具有相同的名称,并且它们始终以本地大小工作,因此无论如何您都无法以8位或16位进行算术
phuclv 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.