草庐IT

c# - 为什么ushort + ushort等于int?

coder 2023-07-11 原文

今天以前,我试图添加两个ushort,但我发现必须将结果转换回ushort。我以为它可能已经成为一个uint(以防止可能的意外溢出?),但令我惊讶的是它是一个int(System.Int32)。

是否有一些聪明的原因,或者可能是因为int被视为“基本”整数类型?

例:

ushort a = 1;
ushort b = 2;

ushort c = a + b; // <- "Cannot implicitly convert type 'int' to 'ushort'. An explicit conversion exists (are you missing a cast?)"
uint d = a + b; // <- "Cannot implicitly convert type 'int' to 'uint'. An explicit conversion exists (are you missing a cast?)"

int e = a + b; // <- Works!

编辑:就像GregS的答案所说的那样,C#规范指出两个操作数(在此示例中为“a”和“b”)都应转换为int。我对这为什么是规范的一部分的根本原因感兴趣:为什么C#规范不允许直接对ushort值进行操作?

最佳答案

简单而正确的答案是“因为C#语言规范是这样说的”。

显然,您对该答案不满意,并且想知道“为什么这么说”。您正在寻找“可信和/或官方来源”,这将有些困难。这些设计决策是在很久以前做出的,在软件工程 Realm 已经有13年的历史了。它们由埃里克·利珀特(Eric Lippert)称呼他们的“老 friend ”制作,他们已经着手做更大更好的事情,因此不在此处发布答案以提供官方消息。

但是,可以推断出它只是可信的风险。任何托管编译器(如C#编译器)都具有这样的约束,即它需要为.NET虚拟机生成代码。 CLI规范中仔细(且易于理解)描述了这些规则。它是Ecma-335的规格,您可以免费下载from here

转到分区III,第3.1和3.2章。它们描述了可用于执行加法的两条IL指令addadd.ovf。单击表2“二进制数值运算”的链接,它描述了那些IL指令允许使用的操作数。请注意,这里仅列出了几种类型。缺少字节和短以及所有无符号类型。仅允许使用int,long,IntPtr和浮点数(float和double)。例如,在用x标记其他约束的情况下,不能将int添加到long中。这些约束并非完全是人为的,它们是基于您可以在可用硬件上合理有效地完成的事情。

任何托管编译器都必须处理此问题才能生成有效的IL。这并不困难,只需将ushort转换为表中较大的值类型,该转换始终有效。 C#编译器选择int,这是表中显示的下一个更大的类型。或通常,将任何操作数转换为下一个最大值类型,以使它们具有相同的类型并满足表中的约束。

但是,现在有了一个新问题,这个问题使C#程序员更加疯狂。添加的结果属于提升类型。在您的情况下,它将为int。因此,将两个ushort值(例如0x9000和0x9000)相加会得到一个完全有效的int结果:0x12000。问题是:这是一个不适合ushort的值。该值溢出。但是它在IL计算中没有溢出,仅在编译器尝试将其填充到ushort中时才溢出。 0x12000被截断为0x2000。令人困惑的不同值,只有在用2或16根手指而不是10根手指计数时才有意义。

值得注意的是,add.ovf指令不处理此问题。这是用于自动生成溢出异常的指令。但事实并非如此,对转换后的int的实际计算并未溢出。

这是真正的设计决策发挥作用的地方。以前的人显然认为,将int结果截断为ushort就是一个错误工厂。必然是。他们决定,您必须承认自己知道添加可能会溢出,并且可以进行添加。他们把它变成了您的问题,主要是因为他们不知道如何使其成为自己的问题,而是仍然生成高效的代码。你必须投。是的,这令人发疯,我敢肯定您也不希望这个问题。

值得注意的是,VB.NET设计人员针对此问题采用了不同的解决方案。他们实际上解决了他们的问题,但并没有因此而失败。您可以添加两个UShorts并将其分配给不带强制转换的UShort。区别在于VB.NET编译器实际上会生成额外的IL以检查溢出条件。那不是便宜的代码,使每次添加的速度慢大约3倍。但是除此之外,这也解释了为什么Microsoft维护两种具有非常相似功能的语言的原因。

长话短说:您付出了代价,因为您使用的类型与现代cpu架构不太匹配。这本身就是使用uint而不是ushort的一个很好的理由。要摆脱ushort的牵引力是困难的,在处理它们的成本超过节省的内存之前,您将需要很多它们。不仅由于CLI规范的限制,x86内核还需要额外的cpu周期来加载16位值,这是因为机器码中的操作数前缀字节。实际上不确定现在是否仍然如此,当我仍然关注计数周期时,它曾经回到过去。一年前的狗。

请注意,通过让C#编译器生成与VB.NET编译器生成的相同的代码,您可以对这些丑陋和危险的转换感到更好。因此,当强制转换被证明是不明智的时,您将获得OverflowException。使用项目>属性>生成选项卡>高级按钮>选中“检查算术上溢/下溢”复选框。仅用于调试版本。为何项目模板没有自动启用此复选框,这是另一个非常神秘的问题,顺便说一下,这个决定是在很久以前做出的。

关于c# - 为什么ushort + ushort等于int?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10065287/

有关c# - 为什么ushort + ushort等于int?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  4. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  5. ruby - 将数组的内容转换为 int - 2

    我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

  6. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  7. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  8. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

  9. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  10. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

随机推荐