看看这个C#代码:
byte x = 1;
byte y = 2;
byte z = x + y; // ERROR: Cannot implicitly convert type 'int' to 'byte'
在byte
(或short
)类型上执行的任何数学运算的结果都会隐式地转换回整数。解决方案是将结果显式地转换回字节:
byte z = (byte)(x + y); // this works
我想知道为什么?它是建筑吗?哲学?
我们有:
int
+ int
= int
long
+ long
= long
float
+ float
= float
double
+ double
= double
为什么不呢:
byte
+ byte
= byte
short
+ short
= short
?一些背景知识:我正在对“小数字”(即< 8)执行一长串计算,并将中间结果存储在一个大数组中。使用字节数组(而不是int数组)更快(因为缓存命中)。但是通过代码传播的大量字节转换使得它更加难以理解。
答案 0 :(得分:209)
代码段的第三行:
byte z = x + y;
实际上意味着
byte z = (int) x + (int) y;
因此,对字节没有+操作,首先将字节转换为整数,并且添加两个整数的结果是(32位)整数。
答案 1 :(得分:162)
就“为什么会发生这种情况”而言,这是因为没有任何运算符由C#定义用于byte,sbyte,short或ushort的算术,就像其他人所说的那样。这个答案是关于为什么没有定义那些运算符。
我认为这基本上是为了表现。处理器具有本机操作,可以非常快速地使用32位进行算术运算。将结果从结果转换回字节可以完成,但如果您实际上不需要这种行为,则会导致性能损失。
我认为这是在一个带注释的C#标准中提到的。寻找...
编辑:令人讨厌的是,我现在已经查看了带注释的ECMA C#2规范,带注释的MS C#3规范和注释CLI规范,并且 none 他们尽可能地提到了这一点。看到。我确定我已经看到了上面给出的理由,但如果我知道在哪里,我就会被打击。道歉,参考粉丝:(答案 2 :(得分:67)
我想到我以前见过这个。来自this article, The Old New Thing:
假设我们生活在幻想世界中 'byte'上的操作导致了什么 '字节'。
byte b = 32;
byte c = 240;
int i = b + c; // what is i?
在这个幻想世界中,我的价值 将是16!为什么?因为这两个 +运算符的操作数都是 字节,所以总和“b + c”计算为 一个字节,导致16由于 整数溢出。 (而且,正如我所说的那样 之前,整数溢出是新的 安全攻击向量。)
编辑:Raymond基本上是在捍卫C和C ++最初采用的方法。在评论中,他以语言向后兼容性为由捍卫了C#采用相同方法的事实。
答案 3 :(得分:57)
ECMA-334声明添加仅在int + int,uint + uint,long + long和ulong + ulong(ECMA-334 14.7.4)上定义为合法。因此,这些是关于14.4.2要考虑的候选操作。因为存在从byte到int,uint,long和ulong的隐式转换,所有加法函数成员都是适用于14.4.2.1的函数成员。我们必须找到14.4.2.3中规则的最佳隐式演员:
将(C1)转换为int(T1)优于将(C2)转换为uint(T2)或ulong(T2),因为:
将(C1)转换为int(T1)优于将(C2)转换为long(T2),因为存在从int到long的隐式转换:
因此使用了int + int函数,它返回一个int。
说这是非常深入的C#规范,这是非常漫长的方式。
CLI仅对6种类型(int32,native int,int64,F,O和&)进行操作。 (ECMA-335分区3第1.5节)
Byte(int8)不是这些类型之一,并且在添加之前会自动强制转换为int32。 (ECMA-335分区3第1.6节)
答案 4 :(得分:25)
表示添加字节并将结果截断回字节的效率低的答案是不正确的。 x86处理器具有专门针对8位数量的整数运算而设计的指令。
事实上,对于x86 / 64处理器,由于必须解码的操作数前缀字节,执行32位或16位操作的效率低于64位或8位操作。在32位机器上,执行16位操作会产生相同的损失,但仍有专用的操作码用于8位操作。
许多RISC架构具有类似的本机字/字节有效指令。那些通常没有存储和转换为某些位长度的符号值的那些。
换句话说,这个决定必须基于对字节类型的感知,而不是由于硬件的潜在低效率。
答案 5 :(得分:13)
我记得曾经读过Jon Skeet的东西(现在找不到,我会继续看),关于字节实际上不会超载+运算符。实际上,当在示例中添加两个字节时,每个字节实际上都是隐式转换为int。结果显然是一个int。现在为什么这是这样设计的,我会等Jon Skeet自己发帖:)
编辑:找到了!关于这个主题here的很好的信息。
答案 6 :(得分:6)
这是因为溢出和携带。
如果添加两个8位数字,它们可能会溢出到第9位。
示例:
1111 1111
+ 0000 0001
-----------
1 0000 0000
我不确定,但我认为ints
,longs
和doubles
会给予更多空间,因为它们非常大。此外,它们是4的倍数,由于内部数据总线的宽度为4字节或32位(64位现在变得更普遍),因此它们对计算机来说更有效。字节和短字的效率要低一些,但它们可以节省空间。
答案 7 :(得分:5)
从C#语言规范1.6.7.5 7.2.6.2二进制数字促销它将两个操作数转换为int,如果它不能适合其他几个类别。我的猜测是他们没有重载+运算符以将byte作为参数,但希望它在某种程度上正常运行,所以他们只使用int数据类型。
答案 8 :(得分:4)
我怀疑C#实际上正在调用operator+
上定义的int
(除非您处于int
块中,否则会返回checked
)并隐式转换您的bytes
/ shorts
至ints
。这就是行为看起来不一致的原因。
答案 9 :(得分:3)
这可能是语言设计者的一个实际决定。毕竟,int是Int32,一个32位有符号整数。无论何时对小于int的类型执行整数运算,无论如何,它将被大多数32位CPU转换为32位signed int。结合小整数溢出的可能性,这可能会使交易失败。它可以帮助您避免连续检查过度/不足流量的繁琐工作,并且当字节表达式的最终结果在范围内时,尽管事实上在某个中间阶段它将超出范围,您将获得正确的结果
另一个想法:必须模拟这些类型的过度/不足流量,因为它不会自然地出现在最可能的目标CPU上。为什么要这么麻烦?
答案 10 :(得分:2)
这在很大程度上是我对此主题的回答,首先提交给类似的问题here。
默认情况下,在计算之前,所有小于Int32的整数运算都会向上舍入为32位。结果是Int32的原因只是在计算之后保持原样。如果检查MSIL算术操作码,则它们使用的唯一整数数字类型是Int32和Int64。这是“按设计”。
如果您希望以Int16格式返回结果,那么如果您在代码中执行强制转换,或者编译器(负责地)在“引擎盖下”发出转换,则无关紧要。
例如,要进行Int16算术:
short a = 2, b = 3;
short c = (short) (a + b);
这两个数字将扩展为32位,被添加,然后被截断回16位,这就是MS的预期。
使用短(或字节)的优势主要是存储大量数据(图形数据,流媒体等)的情况。
答案 11 :(得分:1)
我认为这是一个关于哪个操作更常见的设计决策...如果字节+字节=字节可能会因为在需要int时必须转换为int而烦恼。
答案 12 :(得分:1)
没有为字节定义添加。所以他们被添加到int中。这适用于大多数数学运算和字节。 (注意这是以前的语言,我假设它今天也适用)。
答案 13 :(得分:1)
从.NET Framework代码:
// bytes
private static object AddByte(byte Left, byte Right)
{
short num = (short) (Left + Right);
if (num > 0xff)
{
return num;
}
return (byte) num;
}
// shorts (int16)
private static object AddInt16(short Left, short Right)
{
int num = Left + Right;
if ((num <= 0x7fff) && (num >= -32768))
{
return (short) num;
}
return num;
}
使用.NET 3.5及更高版本简化:
public static class Extensions
{
public static byte Add(this byte a, byte b)
{
return (byte)(a + b);
}
}
现在你可以这样做:
的
byte a = 1, b = 2, c;
c = a.Add(b);
的
答案 14 :(得分:1)
我已经测试了字节和整数之间的性能。
具有int值:
class Program
{
private int a,b,c,d,e,f;
public Program()
{
a = 1;
b = 2;
c = (a + b);
d = (a - b);
e = (b / a);
f = (c * b);
}
static void Main(string[] args)
{
int max = 10000000;
DateTime start = DateTime.Now;
Program[] tab = new Program[max];
for (int i = 0; i < max; i++)
{
tab[i] = new Program();
}
DateTime stop = DateTime.Now;
Debug.WriteLine(stop.Subtract(start).TotalSeconds);
}
}
具有字节值:
class Program
{
private byte a,b,c,d,e,f;
public Program()
{
a = 1;
b = 2;
c = (byte)(a + b);
d = (byte)(a - b);
e = (byte)(b / a);
f = (byte)(c * b);
}
static void Main(string[] args)
{
int max = 10000000;
DateTime start = DateTime.Now;
Program[] tab = new Program[max];
for (int i = 0; i < max; i++)
{
tab[i] = new Program();
}
DateTime stop = DateTime.Now;
Debug.WriteLine(stop.Subtract(start).TotalSeconds);
}
}
结果如下:
字节:3.57s 157mo,3.71s 171mo,3.74s 168mo(CPU〜= 30%
整数:4.05s 298mo,3.92s 278mo,4.28 294mo(CPU〜= 27%
结论:
字节占用更多的CPU,但它消耗的内存更少,而且速度更快(也许是因为分配的字节更少)
答案 15 :(得分:0)
除了所有其他好评之外,我想我会加一点点。很多评论都想知道为什么int,long和几乎任何其他数字类型都不遵循这个规则...返回一个“更大”的类型来响应算术。
很多答案都与性能有关(好吧,32位比8位快)。实际上,8位数仍然是32位CPU的32位数....即使你添加两个字节,cpu操作的数据块也将是32位,无论如何...所以添加int不会比添加两个字节更“快”......它与cpu完全相同。现在,添加两个整数将比在32位处理器上添加两个长度更快,因为添加两个长整数需要更多微指令,因为您使用的数字比处理器字更宽。
我认为导致字节算法导致整数的根本原因非常明确且直截了当:8位不会走得太远! :D对于8位,您的无符号范围为0-255。这是不有很大的工作空间...当你在算术中使用它们时,你将遇到字节限制的可能性非常高。但是,在使用整数,长整数或双精度等时,你用完比特的可能性要低得多......低到足以让我们很少遇到更多需求。
从字节到int的自动转换是 逻辑 ,因为字节的比例非常小。从int到long,float到double等的自动转换是 不合逻辑 ,因为这些数字具有显着的比例。