Windows为什么要检查结构体大小
为什么结构体经常会出现cbSize?
作为开发者的你,可能也注意到了,Windows开发中有一项通用设计规则,即:结构体中通常会有一个数据成员,来表示该结构体的的大小,同时,Windows会严格检查结构体的大小。
举个例子

在上面的结构体中,结构体的大小随着WINVER的定义不同而发生变化。如果你的目标平台为Windows 2000及以上,则结构体中会增加一个hbmpItem的数据成员。如果你使用了Windows 2000版本的结构体并将它传递给Windows NT 4.0,则调用会因为结构体尺寸不匹配而失败。
也许有人会说:”旧版本的操作系统也应该支持那些比它预期更大一些的结构体才对呀?如果一个结构体尺寸比它预期的要大,则表明这个结构体来自一个新版本的应用程序,操作系统应该忽略结构体中它不能解析的部分。”
说老实话,我们也试过上面的做法,但是没能奏效。
又一个例子
考虑下面的结构体,我们有一个函数接收这样一个结构体作为参数。

首先需要说明的是,我们发现有很多开发者会王佳初始化cbSize成员。

因此这个成员会被内存里的随机数据来填充。如果碰巧的是,被填充的数据是一个很大的数,因为这个数刚好大于或等于结构体的大小,所以它会通过结构体尺寸检查,并且代码也能正常工作。
然后,下一个版本的头文件扩展了结构体,使用cbSize来检测调用方是使用的是旧版本还是新版本。 现在,结构体的尺寸还是大于或等于新的cbSize,因此DoImaginaryThing的新版本会觉得:”哦,太棒了,这是有人想要通过IServiceProvider字段提供其他信息。” 当然,因为这个数据来自于内存中的随机数据,调用IServiceProvider::QueryService方法会以崩溃告终。
好了,让我们来考虑下面的使用场景:

头文件的新版本扩展了结构体,并且内存随机数恰好是一个大数字,因此它通过了结构体大小测试,因此它不仅返回了fDance和fSing数据成员,而且还还返回了新增的psp数据成员。但是糟糕的是,调用方是使用旧版本编译的,因此其结构体中并没有psp成员。 psp被写入结构体的末尾,破坏了其内存中的所有内容。 嗯,不错,我们触发了一个缓冲区溢出错误。
如果你足够幸运,内存访问没有触发访问违规,但是程序中还是有一个问题:根据COM引用计数原则,如果一个函数返回了一个接口指针,则调用者在不在需要这个接口指针的时候,需要手动调用Release方法来释放指针。在上面的旧版本中,调用者根本不知道psp数据成员的存在性,所以肯定不会调用psp->Release方法。
这个时候,就会产生内存泄露。
还有一个需要注意的地方是,如果将来编写的新版本程序运行在旧版本操作系统上,会发生什么事呢?
假设有人正在编写打算在新版本上运行的程序。 他们将cbSize设置为较大的新版本结构体大小,并将psp数据成员设置为一个接口指针。现在,有人将该程序并将其运行在旧版本上。 新的结构体大小通过了测试,因此旧版本将接受该结构体并执行ImaginaryThing。 除了v1不支持psp字段外,因此永远不会调用你设置的psp接口指针,这可能不是你预期的行为,因为你添加这个接口指针确实是想着在某种场景下使用它的。
总结
现在,你可能会说:“好吧,那只是有Bug的程序,出问题也正常。”
如果你坚持这种逻辑的话,那么在阅读诸如”微软故意将某个产品设计为与它的竞争对手产品不兼容”之类的新闻时,请做好准备。
最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:这里
- 下一篇: C++工程编译时间分析
- 上一篇: 向子进程传递大量数据的方法
相关推荐
- 实战经验:CentOS环境下搭建OpenCV开发环境
- Posted on 04月10日
- 第 93 期:如何免费延长一年的 Windows 10 技术支持
- Posted on 06月25日
- 第 153 期:下载 Windows 10 ISO 文件并制作启动U盘
- Posted on 08月09日
- 第 234 期:OneDrive 网页版新增深色模式和照片图库新功能
- Posted on 10月05日



评论已关闭。