关于c#:字节+字节=整数…为什么?

byte + byte = int… why?

查看此C代码:

1
2
3
byte x = 1;
byte y = 2;
byte z = x + y; // ERROR: Cannot implicitly convert type 'int' to 'byte'

byte类型(或short类型)执行的任何数学运算的结果都会隐式转换回整数。解决方案是显式地将结果强制转换回一个字节:

1
byte z = (byte)(x + y); // this works

我想知道的是为什么?它是建筑吗?Philosophical?

我们有:

  • int+int=int
  • long+long=long
  • float+float=float
  • double+double=double

为什么不:

  • byte+byte=byte
  • short+short=short

一点背景知识:我正在对"小数字"(即<8)执行一长串计算,并将中间结果存储在一个大数组中。使用字节数组(而不是int数组)更快(因为缓存命中)。但是,广泛的字节转换在代码中传播,使得代码更不可读。


代码段的第三行:

1
byte z = x + y;

实际上意味着

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

所以,字节上没有+运算,字节首先被转换为整数,两个整数相加的结果是一个(32位)整数。


就"为什么会发生这种情况"而言,这是因为没有任何由c定义的用于字节、s byte、short或ushort算术的运算符,正如其他人所说。这个答案是关于为什么没有定义这些操作符。

我相信这基本上是为了表现。处理器具有本地操作,可以非常快地用32位进行算术。自动将结果转换回一个字节是可以做到的,但如果您实际上不希望这种行为发生,则会导致性能损失。

我认为这是在一个带注释的C标准中提到的。看。。。

编辑:令人恼火的是,我现在已经浏览了注释的ecma c_2规范、注释的ms c_3规范和注释cli规范,据我所见,它们都没有提到这一点。我确信我已经看到了上面给出的原因,但如果我知道在哪里,我会被激怒的。抱歉,推荐粉丝:(


我以为我以前在某个地方见过这个。从这篇文章,旧的新东西:

Suppose we lived in a fantasy world
where operations on 'byte' resulted in
'byte'.

1
2
3
byte b = 32;
byte c = 240;
int i = b + c; // what is i?

In this fantasy world, the value of i
would be 16! Why? Because the two
operands to the + operator are both
bytes, so the sum"b+c" is computed as
a byte, which results in 16 due to
integer overflow. (And, as I noted
earlier, integer overflow is the new
security attack vector.)

编辑:雷蒙德是防守,本质上,C和C++是最初采取的方法。在评论中,他辩护了这样一个事实:基于语言向后兼容性,C采取了同样的方法。


C.*

ECMA-334规定,添加仅在int+int、uint+uint、long+long和ulong+ulong(ECMA-334 14.7.4)上定义为合法。因此,这些是关于14.4.2的候选操作。由于存在从字节到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节)

字节(int8)不是这些类型中的一种,并且在加法前自动强制为int32。(ECMA-335分区3第1.6节)


表示添加字节和将结果截断回一个字节效率低下的回答是错误的。x86处理器有专门为8位数量的整数运算设计的指令。

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

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

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


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

编辑:找到了!关于这个主题的很好的信息。


这是因为溢出和携带。

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

例子:

1
2
3
4
  1111 1111
+ 0000 0001
-----------
1 0000 0000

我不确定,但我假设intslongs、anddoubles有更多的空间,因为它们的尺寸相当大。此外,它们是4的倍数,这对计算机处理效率更高,因为内部数据总线的宽度为4字节或32位(64位现在越来越普遍)。字节和短字节效率稍低,但它们可以节省空间。


从C语言规范1.6.7.5 7.2.6.2二进制数字升级中,如果不能将两个操作数放入其他几个类别中,它会将两个操作数转换为int。我的猜测是,它们没有重载+运算符以字节作为参数,但希望它能正常工作,所以它们只使用int数据类型。

C语言规范


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


这可能是语言设计者的一个实际决定。毕竟,int是一个int32,一个32位有符号整数。每当对小于int的类型执行整数操作时,大多数32位CPU都会将其转换为32位有符号int。再加上小整数溢出的可能性,这可能就决定了交易的成败。它省去了持续检查流量过/不足的繁琐工作,当字节上表达式的最终结果将在范围内时,尽管在某个中间阶段它将超出范围,但您仍然可以得到正确的结果。

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


这在很大程度上是我关于这个主题的答案,首先提交给这里的一个类似问题。

在默认情况下,所有整数小于Int32的操作在计算之前都会四舍五入到32位。结果为Int32的原因只是简单地保留计算后的结果。如果检查msil算术操作码,则它们操作的唯一整数数字类型是Int32和Int64。这是"按设计"。

如果您希望结果返回到int16格式,那么如果您执行了强制转换代码,或者编译器(在底层)发出转换,则与此无关。

例如,要执行Int16算术运算:

1
2
3
short a = 2, b = 3;

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

这两个数字将扩展到32位,进行相加,然后截断为16位,这就是微软想要的。

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


我测试了byte和int之间的性能。使用int值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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);
    }
}

字节值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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,但它占用了LES内存,而且速度更快(可能是因为要分配的字节更少)


没有为字节定义加法。因此,它们被强制转换为int进行加法运算。大多数数学运算和字节都是这样。(请注意,这是以前的语言,我假设它在今天是正确的)。


我认为这是一个设计决策,关于哪个操作更常见…如果byte+byte=byte,那么当结果需要int时,可能会有更多的人被强制转换成int。


来自.NET框架代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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及更高版本进行简化:

1
2
3
4
5
6
7
public static class Extensions
{
    public static byte Add(this byte a, byte b)
    {
        return (byte)(a + b);
    }
}

现在你可以做到:

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

除了所有其他伟大的评论,我想我会加一点小道消息。很多评论都想知道为什么in t、long和几乎所有其他数字类型都不遵循这个规则……返回一个"更大"的类型来响应算术。

很多答案都与性能有关(32位比8位快)。实际上,8位数字对32位CPU来说仍然是32位数字……即使添加两个字节,CPU操作的数据块也将是32位,不管怎样……所以添加ints不会比添加两个字节更快……对CPU来说是一样的。现在,添加两个整数比在32位处理器上添加两个长整型更快,因为添加两个长整型需要更多的微操作,因为您处理的数字比处理器字宽。

我认为导致字节算术产生整数的根本原因是非常清楚和直截了当的:8它只是没有走多远!:d有8位,无符号范围为0-255。这不是一个很好的工作空间……在算术中使用字节限制的可能性很高。但是,在使用int、long或double等时,您将耗尽位的机会非常低……足够低,我们很少遇到需要更多位的情况。

字节到int的自动转换是合乎逻辑的,因为字节的小数位数太小了。从int到long、float到double等的自动转换是不符合逻辑的,因为这些数字具有显著的规模。