还不明白可空类型原理? 我可要挖到底了

一:背景

1. 讲故事

做好自媒体到现在有一个月了,关注我的兄弟应该知道我产出了不少文章,号里的粉丝也多起来了,我也尽最大努力做到有问必回,现在是基础的、高深的问题都接踵而来,可我也只是一只小菜鸟,想飞也飞不动了(┬_┬),昨天号里有位朋友被面试官问到可空类型的原理,回答的不好,面试官也是,面就面呗,又给不了多少银子,还动不动就原理,哪有那么多原理,搞得双方都尴尬????????????。

二:给我锄头我要挖到底

这种问题要怎么挖呢?我在之前的文章也聊过,C#代码到机器码中间有两个编译过程,一个是csc编译后的IL代码,一个是jit编译后的native代码,所以搞懂IL代码和native代码就是我们要深究的方向,我还是把那篇文章的图拿过来。

为了方便演示,我就定义一个int?类型,接收非null和null两种情况。

static void Main(string[] args){int? num1 = 10;int? num2 = null;Console.WriteLine("执行结束啦!");Console.ReadLine();}

1. 挖IL代码

挖IL代码简单,用ILSPY小工具就可以了,编译后生成的IL代码如下:


.method private hidebysig static void Main (string[] args) cil managed
{// Method begins at RVA 0x2048// Code size 36 (0x24).maxstack 2.entrypoint.locals init ([0] valuetype [mscorlib]System.Nullable`1<int32> num1,[1] valuetype [mscorlib]System.Nullable`1<int32> num2)IL_0000: nopIL_0001: ldloca.s 0IL_0003: ldc.i4.s 10IL_0005: call instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)IL_000a: ldloca.s 1IL_000c: initobj valuetype [mscorlib]System.Nullable`1<int32>IL_0012: ldstr "执行结束啦!"IL_0017: call void [mscorlib]System.Console::WriteLine(string)IL_001c: nopIL_001d: call string [mscorlib]System.Console::ReadLine()IL_0022: popIL_0023: ret
} // end of method Program::Main

这IL代码还是非常易懂的,比汇编简单多啦(┬_┬),可以看到int ? 就是 System.Nullable<int32> ,然后从valuetype 标记可以看到这玩意是个值类型,所以把上面的代码回转成C#代码就是下面这样。

{static void Main(string[] args){//int? num1 = 10;//int? num2 = null;Nullable<int> num3 = new Nullable<int>(10);Nullable<int> num4 = new Nullable<int>();Console.WriteLine("执行结束啦!");Console.ReadLine();}

很简单吧,那怎么输出num3和num4呢?直接Console.WriteLine就好了。

这里你肯定有一个疑问,为什么num3输出10,而num4什么都没输出呢?哈哈,这是因为Nullable的ToString()被重写了,再来看下ToString被重写成啥样了,代码如下:

public struct Nullable<T> where T : struct
{private bool hasValue;internal T value;[NonVersionable][__DynamicallyInvokable]public Nullable(T value){this.value = value;hasValue = true;}[__DynamicallyInvokable]public override string ToString(){if (!hasValue){return "";}return value.ToString();}
}

可以看到ToString方法里要么返回空字符串要么返回你在构造函数中塞入的value,这这么简单,IL代码挖到这里就可以了。

2. 挖机器代码

要看num1和num2的机器代码,其实也就是看 Nullable<T> 的内存布局方式,这里我使用windbg,还是使用 !clrstack -l 查看线程栈。

int? num1 = 10;int? num2 =null;0:007> ~0s
ntdll!ZwReadFile+0x14:
00007ffc`ec11aa64 c3              ret
0:000> !clrstack -l
OS Thread Id: 0x5364 (0)Child SP               IP Call Site
ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 21]LOCALS:0x00000018a9dfeaf8 = 0x0000000a000000010x00000018a9dfeaf0 = 0x000000000000000000000018a9dfed08 00007ffcd5b66c93 [GCFrame: 00000018a9dfed08]

LOCALS中可以看到,num1和num2的线程栈上存放的内容分别是0x0000000a00000001和 0x0000000000000000, 不过这值也挺奇怪的,一个是1一个是0。。。我们用 dd 命令把地址转储出来。


0:000> dd 0x00000018a9dfeaf8 
00000018`a9dfeaf8  00000001 0000000a a9dfec08 00000018
0:000> dd 0x00000018a9dfeaf0 
00000018`a9dfeaf0  00000000 00000000 00000001 0000000a

在num1的内存区域中有一个十六进制值 0000000a ,这就是十进制的10,那前面的 00000001 是什么东西呢?大家不要忘啦, int?是语法糖, 你现在看的是 Nullable<T> 哈。。。

看清楚啦,这个结构体里面有两个值类型字段,自然 00000001 就是 hasValue=true啦。 num2也就好理解了,两个默认值也就是两个0了。00000000 00000000

三:有意外发现

1. int? 比 int 要占用更多的内存

如果你的内存数据量特别大的话,你就要当心了,int? 比 int 在x64上要多占4个字节,也就是多一倍,无论线程栈还是托管堆。

2. 为什么bool要占用 4字节空间?

<1> 线程栈上的演示

肯定有人比较疑惑,bool在C#中不就是一个字节嘛?你怎么说是4个字节呢?你要是问我,我只能说从windbg上看就是这样的,x64系统的线程栈上就是以4个字节为一个单位,你不信的话,我就在代码中定义不同字段的 值类型,你看看在线程栈上的分布不就好啦,以事实说话。

            byte b1 = byte.MaxValue;byte b2 = byte.MaxValue;short b3 = short.MaxValue;short b4 = short.MaxValue;int b5 = int.MaxValue;int b6 = int.MaxValue;0:000> !clrstack -l
OS Thread Id: 0xa98 (0)
ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 25]LOCALS:0x000000a8395fedbc = 0x00000000000000ff0x000000a8395fedb8 = 0x00000000000000ff0x000000a8395fedb4 = 0x0000000000007fff0x000000a8395fedb0 = 0x0000000000007fff0x000000a8395fedac = 0x000000007fffffff0x000000a8395feda8 = 0x000000007fffffff

然后把最小的地址0x000000a8395feda8 转储出来。


0:000> dd 0x000000a8395feda8
000000a8`395feda8  7fffffff 7fffffff 00007fff 00007fff
000000a8`395fedb8  000000ff 000000ff 395feec8 000000a8
000000a8`395fedc8  395fefc8 000000a8 395fee00 000000a8
000000a8`395fedd8  d5b66c93 00007ffc 98e72d30 000001ee
000000a8`395fede8  76504140 00007ffc 00000000 00000000
000000a8`395fedf8  00000000 00007ffc 395feef0 000000a8
000000a8`395fee08  971d0b20 000001ee 00000000 00000000
000000a8`395fee18  d5b66b79 00007ffc 00000000 00000000

对比一下可以看到上面的 7fffffff, 00007fff,000000ff就是相应的int,short,byte的MaxValue, 都是占用4个字节的空间,没问题吧。

<2> 托管堆演示

var arr1 = new int[] { 10 };var arr2 = new int?[] { 14 };0:000> !clrstack -l
OS Thread Id: 0x23f8 (0)
000000859a1fec60 00007ffc76630967 ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 32]LOCALS:0x000000859a1feca0 = 0x000002773cb32d700x000000859a1fec98 = 0x000002773cb32d90000000859a1feeb8 00007ffcd5b66c93 [GCFrame: 000000859a1feeb8]
0:000> !do 0x000002773cb32d70
Name:        System.Int32[]
MethodTable: 00007ffcd2d58538
EEClass:     00007ffcd2ec5918
Size:        28(0x1c) bytes
Array:       Rank 1, Number of elements 1, Type Int32 (Print Array)
Fields:
None
0:000> !do 0x000002773cb32d90
Name:        System.Nullable`1[[System.Int32, mscorlib]][]
MethodTable: 00007ffcd3fb2058
EEClass:     00007ffcd30221a0
Size:        32(0x20) bytes
Array:       Rank 1, Number of elements 1, Type VALUETYPE (Print Array)
Fields:
None0:000> !objsize 0x000002773cb32d70
sizeof(000002773cb32d70) = 32 (0x20) bytes (System.Int32[])
0:000> !objsize 0x000002773cb32d90
sizeof(000002773cb32d90) = 32 (0x20) bytes (System.Nullable`1[[System.Int32, mscorlib]][])

可以看到,一个是28byte,一个是32byte,多出来的就是那个hasValue哈,有一点要注意了,用!objsize打出来都是32byte,这是因为28byte要靠8对齐就变成32byte啦, 然后我把两个值类型转储出来,如下图:

四:总结

挖到这里,不知道可挖到了面试官的盲区啦????,总之int?就是 Nullable<T>, 而且可空比非可空多4个字节的空间,最后大家要看自己情况使用啦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309943.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[设计模式]策略模式

策略模式:定义了一系列算法&#xff0c;并将每一个算法封装起来&#xff0c;而且使它们还可以相互替换。 策略模式让算法独立于使用它的客户而独立变化。 代码如下: #include <iostream> using namespace std;class WeaponStrategy { public:virtual void useWeapon()…

蜘蛛牌 HDU - 1584(搜索——达到先让某些段先结合,达最优解)

题意&#xff1a; 一排杂乱的牌&#xff0c;牌间距为1&#xff0c;每次移动只能将小的牌&#xff0c;移动到较大牌上&#xff0c;最终使得牌从小到大排好在一堆。问移动的最小距离。 题目&#xff1a; 蜘蛛牌是windows xp操作系统自带的一款纸牌游戏&#xff0c;游戏规则是这…

oracle 启动实例配置,centOS 7配置单实例oracle自启动

1、修改/etc/oratab(oracle用户操作)vi /etc/oratabtestdb:/u01/app/oracle/product/11.2.0/db_1:N改为testdb:/u01/app/oracle/product/11.2.0/db_1:Y###注意替换对应 ORACLE_SID 和 ORACLE_HOME2、修改$ORACLE_HOME/bin/dbstart(oracle用户操作)echo $ORACLE_HOMEvi $ORACLE_…

浅析微软的网关项目 -- ReverseProxy

浅析微软的网关项目--ReverseProxyIntro最近微软新开了一个项目 ReverseProxy &#xff0c;也叫做 YARP(A Reverse Proxy)官方介绍如下&#xff1a;YARP is a reverse proxy toolkit for building fast proxy servers in .NET using the infrastructure from ASP.NET and .NET.…

[设计模式]模板方法模式

模板方法模式: 定义一个操作中算法的框架&#xff0c;而将一些步骤延迟到子类中。模仿方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 代码如下: #include <iostream> using namespace std;class DrinkTemplate { public:virtual void Boi…

Max Sum Plus Plus HDU - 1024(动态规划求最大M子段和)

题意&#xff1a; ----最大M子段和问题 给定由 n个整数&#xff08;可能为负整数&#xff09;组成的序列以及一个正整数 m&#xff0c;要求确定序列的 m个不相交子段&#xff0c;使这m个子段的总和达到最大&#xff0c;求出最大和。 题目&#xff1a; Now I think you have …

oracle游标的常用属性,Oracle基础知识(二十六) - Oracle游标常用属性

Oracle游标相信大家都不陌生&#xff0c;下面就为您详细介绍Oracle游标的常用属性&#xff0c;如果您对Oracle游标方面感兴趣的话&#xff0c;不妨一看。Oracle游标常用属性&#xff1a;%FOUND&#xff1a;变量最后从游标中获取记录的时候&#xff0c;在结果集中找到了记录。%N…

差距(分享)

非985大学生, 你和别人的差距在哪里?&#xff08;转&#xff09; 非985大学生, 你和别人的差距在哪里? 中国青年报03-24 在知乎上看到这样一段话&#xff1a; “渣学校意味着渣教学&#xff0c;渣教学意味着渣学历&#xff0c;渣学历意味着渣就业&#xff0c;就算以后考了研究…

在微服务框架Demo.MicroServer中添加SkyWalking+SkyApm-dotnet分布式链路追踪系统

1.APM工具的选取Apm监测工具很多&#xff0c;这里选用网上比较火的一款Skywalking。Skywalking是一个应用性能监控(APM)系统&#xff0c;Skywalking分为服务端Oap、管理界面UI、以及嵌入到程序中的探针Agent部分&#xff0c;大概工作流程就是在程序中添加探针采集各种数据发送给…

[C++11]constexpr修饰常量表达式

常量表达式和非常量表达式的计算时机不同&#xff0c;非常量表达式只能在程序运行阶段算出结果&#xff0c;但是常量表达式的计算往往发生在程序的编译阶段&#xff0c;这可以极大提高程序的执行效率。 constexpr定义一个常量。 代码如下: #include <iostream> using …

oracle sql execute elapsed time,SQL ordered by Elapsed Time 脚本

--提取&beg_snap 、&end_snapselect * from dba_hist_snapshot x ;--提取&dbidselect * from v$database;--提取$inst_numselect * from v$instance;运行的时候输入上面的值(如果需要输入多次&#xff0c;那么输入同样值即可)select from (select round(nvl((sqt.e…

计算机组成原理期末复习题

地址总线A15~Ao(低),存储空间(按字节编址)分配如下 2000H~3FFFH为ROM区, 5000H~6FFFH为RAM区。用 ROM芯片(4Kx4)和RAM芯片(4Kx4)组成该存储器。请回答 &#xff1a;(1)分别需要ROM和RAM多少片? (2)用二进制形式写出每组芯片的地址范围,并说明可以通过哪些地址位来形成片选信号…

干货分享:如何使用Kubernetes的Ingress API

导语以Kubernetes的Kong为例&#xff0c;聊聊当前流行的开源且与云无关的Ingress控制器。正文您可以通过使用诸如Kong for Kubernetes的Ingress控制器&#xff08;使用自定义资源定义并提供许多插件&#xff09;来极大地扩展Ingress资源的功能。Kubernetes正在整个技术行业中得…

[PAT乙级]1029 旧键盘

旧键盘上坏了几个键&#xff0c;于是在敲一段文字的时候&#xff0c;对应的字符就不会出现。现在给出应该输入的一段文字、以及实际被输入的文字&#xff0c;请你列出肯定坏掉的那些键。 输入格式&#xff1a; 输入在 2 行中分别给出应该输入的文字、以及实际被输入的文字。每…

表空间oracle查询,Oracle表和表空间查询

用户查询查询和用户相关的数据创建用户CREATE USER userIDENTIFIED BY password[DEFAULT TABLESPACE tablespace][TEMPORARY TABLESPACE tablespace]查询所有用户数select username from dba_users;查询当前实例名称select instance_name from v$instance;表空间查询查询用户表…

计算机组成原理期末复习往年卷子

1. I/O设备的编址方式通常有___统一编址__和_独立编址__两种方式。P145 2&#xff0e;Cache是一种高速缓冲存储器&#xff0c;是为了解决____CPU____和___主存____之间速度不匹配而采用的一项重要技术。P124 3&#xff0e;在计算机系统中&#xff0c;I/O设备与主机传递消息的…

.NET Core接入ElasticSearch 7.5

写在前面最近一段时间&#xff0c;团队在升级ElasticSearch&#xff08;以下简称ES&#xff09;&#xff0c;从ES 2.2升级到ES 7.5。也是这段时间&#xff0c;我从零开始&#xff0c;逐步的了解了ES&#xff0c;中间也踩了不少坑&#xff0c;所以特地梳理和总结一下相关的技术点…

[C++11]字符串原始字面量

代码如下: #include <iostream> #include <string> using namespace std;int main() {string str1 R"(D:\hello\world\test.txt)";cout << str1 << endl;string str2 R"(dsdasasdasasda asdagdfhadagd)";cout << str2 &l…

oracle实现序列,oracle中创建序列和自增长列的实现方式

众所周知&#xff0c;在SQL Server中设置自增长列很简单&#xff0c;但是在ORACLE中则不然&#xff0c;现在介绍一下ORACLE中自增长列如何实现&#xff1a;1.创建序列。举例如下&#xff1a;create sequence seq_personincrement by 1start with 1nomaxvalue --不设最大值nocy…

ASP.NET Core on K8s学习之旅(13)Ocelot API网关接入

【云原生】| 作者/Edison Zhou这是恰童鞋骚年的第232篇原创文章上一篇介绍了Ingress的基本概念和Nginx Ingress的基本配置和使用&#xff0c;考虑到很多团队都在使用Ocelot作为API网关&#xff08;包括我司&#xff09;做了很多限流和鉴权的工作&#xff0c;因此本篇介绍一下如…