字节+字节= int…为什么?

看看这段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

我想知道的是为什么?是建筑吗?哲学吗?

我们有:

  • __abc0 + __abc0 = __abc0
  • __abc0 + __abc0 = __abc0
  • __abc0 + __abc0 = __abc0
  • __abc0 + __abc0 = __abc0

所以为什么不呢:

  • __abc0 + __abc0 = __abc0
  • __abc0 + __abc0 = __abc0 ?

一点背景知识:我正在执行一长串“小数字”的计算(即<8)将中间结果存储在一个大数组中。使用字节数组(而不是int数组)(因为缓存命中)。但是大量的字节强制转换散布在代码中,使得代码更加难以阅读。

78230 次浏览

这是由于溢出和携带。

如果您将两个8位数字相加,它们可能会溢出到第9位。

例子:

  1111 1111
+ 0000 0001
-----------
1 0000 0000

我不确定,但我假设intslongs和__abc2被赋予了更多的空间,因为它们非常大。此外,它们是4的倍数,这对计算机来说更有效,因为内部数据总线的宽度是4字节或32位(64位现在越来越普遍)宽。Byte和short的效率稍低,但它们可以节省空间。

我以前在什么地方见过这个。从这篇文章,旧的新事物:

假设我们生活在一个幻想的世界里 对'byte'的操作导致了什么 “字节”。< / p >
byte b = 32;
byte c = 240;
int i = b + c; // what is i?

在这个幻想世界中,i的值 应该是16岁!为什么?因为这两个 +操作符的操作数都是 字节,因此“b+c”的和计算为 一个字节,由于 整数溢出。(而且,正如我所指出的 之前,整数溢出是新的 安全攻击向量)

编辑: Raymond实际上是在捍卫C和c++最初采用的方法。在评论中,他从语言向后兼容的角度为c#采用了相同的方法进行了辩护。

我认为这是一个关于哪种操作更常见的设计决策……如果byte+byte = byte,可能更多的人会被强制转换为int类型的结果所困扰。

我怀疑c#实际上是在调用int上定义的operator+(它返回int,除非你在checked块中),并隐式地将你的bytes/shorts转换为ints。这就是为什么行为看起来不一致。

我记得曾经读过Jon Skeet(现在找不到了,我会继续找)关于字节如何实际上不会重载+操作符的内容。事实上,当像您的示例中那样添加两个字节时,每个字节实际上都被隐式转换为int。其结果显然是一个整型。至于为什么要这样设计,我将等待乔恩·斯基特自己发布:)

找到了!关于这个主题在这里的很棒的信息。

没有为字节定义加法。所以它们被转换为int类型进行加法运算。对于大多数数学运算和字节来说都是如此。(请注意,这是在旧语言中使用的方式,我假设它在今天仍然适用)。

对于语言设计者来说,这可能是一个实际的决定。毕竟,int是一个32位有符号整数。无论何时你对一个小于int的类型执行整数运算,它都会被大多数32位CPU转换为32位有符号int。这一点,再加上小整数溢出的可能性,可能促成了交易。它使您避免了连续检查溢出/欠流的繁琐工作,并且当字节表达式的最终结果在范围内时,尽管事实上在某个中间阶段它会超出范围,但您会得到正确的结果。

另一种想法是:必须模拟这些类型上的溢出/欠流,因为它不会自然地发生在最可能的目标cpu上。何苦呢?

就“为什么会发生这种情况”而言,这是因为正如其他人所说的那样,c#中没有为byte、sbyte、short或ushort的算术定义任何操作符。这个答案是关于为什么那些操作符没有定义。

我相信这主要是为了性能。处理器具有本地操作,可以非常快速地处理32位的算术。从结果自动转换回字节可以是可以完成的,但如果你实际上不想要这种行为,则会导致性能损失。

I 认为这在一个带注释的c#标准中提到过。看……

编辑:令人恼火的是,我现在已经查看了带注释的ECMA c# 2规范,带注释的MS c# 3规范和注释的CLI规范,其中没有一个提到了这一点。我已经看到了上面给出的原因,但如果我知道在哪里,我就被吹了。对不起,参考粉丝们:(

根据c#语言规范1.6.7.5 7.2.6.2二进制数值提升,如果不能将操作数归入其他类别,则将两个操作数都转换为int。我的猜测是,他们没有重载+操作符以将字节作为参数,但希望它的行为有点正常,所以他们只是使用int数据类型。

c#语言规范 .

代码片段的第三行:

byte z = x + y;

实际上意味着

byte z = (int) x + (int) y;

因此,对字节没有+操作,字节首先被转换为整数,两个整数相加的结果是一个(32位)整数。

在添加字节和将结果截断回字节时显示一些低效的答案是不正确的。X86处理器有专门为8位数的整数运算而设计的指令。

事实上,对于x86/64处理器,执行32位或16位操作的效率低于64位或8位操作,因为必须对操作数前缀字节进行解码。在32位机器上,执行16位操作需要同样的代价,但是仍然有专门的8位操作的操作码。

许多RISC架构都有类似的本地字/字节高效指令。那些通常没有存储并转换为某个比特长度的signed值的类型。

换句话说,这个决定必须基于对字节类型用途的感知,而不是由于硬件的底层低效率。

c#

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)更好,因为:

  • 如果T1是int, T2是uint或ulong, C1是更好的转换。

将(C1)转换为int(T1)比将(C2)转换为long(T2)更好,因为从int转换为long存在隐式转换:

  • 如果存在从T1到T2的隐式转换,且不存在从T2到T1的隐式转换,则C1是更好的转换。

因此使用int+int函数,该函数返回int型。

可以说它深埋在c#规范中。

CLI

CLI只对6种类型(int32、native int、int64、F、O和&)起作用。(ECMA-335分区3节1.5)

Byte (int8)不是这些类型之一,在添加之前会自动强制转换为int32。(ECMA-335分区3节1.6)

除了所有其他伟大的评论,我想我要添加一个小花絮。很多评论都想知道为什么int、long和几乎任何其他数字类型都不遵循这个规则…返回一个“更大”的类型以响应算术。

很多答案都与性能有关(好吧,32位比8位快)。实际上,对于32位CPU来说,8位数字仍然是32位数字....即使你增加了两个字节,CPU操作的数据块也将是32位的…因此,添加整数并不会比添加两个字节“更快”……对cpu来说都一样。现在,在32位处理器上,加两个整型将比加两个长型更快,因为加两个长型需要更多的微操作,因为你处理的数字比处理器字宽。

我认为导致字节算术结果为整数的根本原因是非常清楚和直接的:8位只是没有走多远!对于8位,你有一个0-255的无符号范围。这是很大的工作空间…在算术中使用它们时,你遇到字节限制的可能性非常高。然而,在处理整型、长型或双精度数等数据时,用完位的几率会大大降低……低到我们很少需要更多。

从字节到int的自动转换是< em >逻辑< / em >,因为字节的规模太小了。自动转换从int型到long型,浮点型到double型等是< em >不合逻辑< / em >,因为这些数字有显著的比例。

这是我对这个话题的大部分回答,首先提交给一个类似的问题在这里

默认情况下,所有小于Int32的整数运算在计算前四舍五入到32位。结果为Int32的原因仅仅是让它在计算后保持原样。如果检查MSIL算术操作码,它们操作的唯一整型数字类型是Int32和Int64。这是“故意的”。

如果您希望结果以Int16格式返回,则在代码中执行强制转换或编译器(假设)在“底层”发出转换都无关紧要。

例如,要执行Int16算术:

short a = 2, b = 3;


short c = (short) (a + b);

这两个数字将扩展为32位,然后相加,然后截短为16位,这是MS所希望的。

使用短(或字节)的优势主要是在有大量数据(图形数据、流等)的情况下存储。

从.NET框架代码:

// 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;
}

简化 with .NET 3.5及以上版本:

public static class Extensions
{
public static byte Add(this byte a, byte b)
{
return (byte)(a + b);
}
}

现在你可以做:

< b >

byte a = 1, b = 2, c;
c = a.Add(b);

< / b >

我测试了字节和int之间的性能 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%
int: 4.05s 298mo, 3.92s 278mo, 4.28 294mo with CPU ~= 27%
结论: < br > 字节使用更多的CPU,但它消耗更少的内存,它更快(可能是因为分配的字节更少)