对于C++这种强类型的语言,明确的类型既带来了执行的高效,又让错误的发生提前到编译期。所以像const这类体现设计者意图的关键字,可以隐性的透露给我们它描述的对象的使用边界。它是我们的朋友,我们要学会和它相处,而不是改变它。(转载请指明出于breaksoftware的csdn博客)

我们来看一个试图改变这个好朋友的案例

class Base {
public:Base() : _name("Base") {}const char* get_name() const {return _name;}
private:const char _name[32];
};

Base类有个get_name方法返回了一个const char*。它返回的是该类的成员变量_name——即类名"base"。

现在有个同学试图继承该类,于是他增加了如下代码

class Derive : public Base {
public:Derive() {const char* base_name = Base::get_name();strcpy(const_cast<char*>(base_name), "Derive");}
};

Derive的构造函数调用了Base的get_name方法获取了一个const char*变量base_name。他试图去修改这个地址空间的数据,让其填充自己类的名称"Derive"。由于strcpy需要第一个参数是char*型的,于是他使用const_cast关键字“剥夺”了base_name的const属性,让编译通过。

目前这段代码还可以正确执行,但是之后一个同学对Base类的优化,将彻底触发修改const属性引发的灾难。

class Base {
public:const char* get_name() const {return _name;}
private:const char* _name = "Base";
}

这样编译出来的代码,最终在执行期会崩溃。

先分析Base的get_name方法的反汇编代码

push    ebp
mov     ebp, esp
sub     esp, 0CCh
push    ebx
push    esi
push    edi
push    ecx
lea     edi, [ebp+var_CC]
mov     ecx, 33h
mov     eax, 0CCCCCCCCh
rep stosd
pop     ecx
mov     [ebp+var_8], ecx
mov     eax, [ebp+var_8]
mov     dword ptr [eax], offset aBase ; "Base"
mov     eax, [ebp+var_8]
pop     edi
pop     esi
pop     ebx
mov     esp, ebp
pop     ebp
retn

第15行将"Base"字符串的地址保存到寄存器eax指向的地址空间中。之后该方法返回

push    offset Source   ; "Derive"
mov     eax, [ebp+Dest]
push    eax             ; Dest
call    j_strcpy_0

调用了strcpy方法,其中eax还是"Base"字符串的首地址。

崩溃出现在strcpy方法中,出错的地址也是“Base"字符串的首地址。

为什么写这个地址会出错,我们看下get_name中aBase的地址

.rdata:0041DB30 aBase           db 'Base',0             ; DATA XREF: sub_412460+26↑o
.rdata:0041DB35                 db    0
.rdata:0041DB36                 db    0
.rdata:0041DB37                 db    0
.rdata:0041DB38                 db    0

可以见到这个地址保存在rdata区域。rdata是readonly-data的意思,即这块地址空间是只读的,所以往其中写数据会报错。

由于我们在修改后的Base类中,让成员变量_name指向了一个字面量。这个字面量作为常量,它会保存在PE/ELF文件的只读数据区域。关于什么信息会保存在只读区域,以及还有什么其他区域,大家可以在网上搜索PE/ELF文件格式的说明。

最后从语义角度来说,可以认为Derive的作者违背了编译器对关键字const的约束,即违背了一种约定导致的。所以我们尽量别用const_cast这种试图绕过编译器的“小聪明”手段。

bug诞生记——const_cast引发只读数据区域写违例相关推荐

  1. bug诞生记——临时变量、栈变量导致的双杀

    这是<bug诞生记>的第一篇文章.本来想起个文艺点的名字,比如<Satan(撒旦)来了>,但是最后还是想让这系列的重心放在"bug的产生过程"和" ...

  2. bug诞生记——隐蔽的指针偏移计算导致的数据错乱

    C++语言为了兼容C语言,做了很多设计方面的考量.但是有些兼容设计产生了不清晰的认识.本文就将讨论一个因为认知不清晰而导致的bug.(转载请指明出于breaksoftware的csdn博客) clas ...

  3. bug诞生记——信号(signal)处理导致死锁

    这个bug源于项目中一个诡异的现象:代码层面没有明显的锁的问题,但是执行时发生了死锁一样的表现.我把业务逻辑简化为:父进程一直维持一个子进程.(转载请指明出于breaksoftware的csdn博客) ...

  4. bug诞生记——不定长参数隐藏的类型问题

    这个bug的诞生源于项目中使用了一个开源C库.由于对该C库API不熟悉,一个不起眼的错误调用,导致一系列诡异的问题.最终经过调试,我们发现发生了内存覆盖问题.为了直达问题根节,我将问题代码简化如下(转 ...

  5. 张小龙的微信帝国诞生记

    2010年11月20日这一天,在广州,一个六七人的产品小组正式组建.2011年10月1日,这个产品小组的一款产品登上了中国移动互联网即时通讯工具软件第一的位置. 这款产品叫"微信" ...

  6. “独裁”的张小龙和他的微信帝国诞生记

    2010年11月20日这一天,在广州,一个六七人的产品小组正式组建.2011年10月1日,这个产品小组的一款产品登上了中国移动互联网即时通讯工具软件第一的位置. 这款产品叫" 微信" ...

  7. 谷歌Gmail诞生记:十年回首

    美国<时代>周刊网络版今天刊登题为<Gmail诞生记:10年前鲜为人知的故事>(How Gmail Happened: The Inside Story of Its Laun ...

  8. 2019领克车展 Max Co币机诞生记

    领克车展 Max Co币机诞生记 网上拿张现场图 先引用一篇时下2019上海车展文章的段落 上海车展新车满满,领克展台玩起了"骚操作" https://chejiahao.auto ...

  9. Apache DolphinScheduler 诞生记

    Apache DolphinScheduler 诞生记 DolphinScheduler,简称"DS", 中文名 "小海豚调度"(海豚聪明.人性化,又左右脑可互 ...

最新文章

  1. Redis 秒杀实战
  2. sudo出现sudo:must be setuid root问题的解决方法
  3. Semver(语义化版本号)扫盲
  4. python版本的策略模式
  5. CVPR 2020 | ActBERT: 自监督多模态视频文字学习
  6. 洛谷P1157----组合数的输出
  7. aws 删除ec2实例_如何在AWS中启动EC2实例
  8. 开灯问题---------简单模拟
  9. 函数返回字符串指针C语言,C语言:利用指针和函数调用编写字符串拷贝函数strcpy...
  10. 【JSOI2007】【BZOJ1029】【codevs2913】建筑抢修,贪心与堆
  11. spss方差分析_交叉设计及SPSS多因素方差分析
  12. WinEdt LaTex(五)—— 内容的排版
  13. 昨晚我把900W+数据,从17s优化到300ms
  14. 若要调试此模块,请将其项目生成配置更改为“调试”模式。若要取消显示此消息,请禁用“启动时若没有用户代码则发出警告”调试器选项。...
  15. 21世纪需要的七种人才—李开复
  16. 布莱克曼哈尔窗matlab,基于matlab的布莱克曼窗函数法设计的低通滤波器
  17. 单变量微积分重点(2)
  18. ICG-NHS吲哚菁绿-琥珀酰亚胺脂的相关简介;CAS: 1622335-40-3
  19. 卷组删除pv_如何安全的删除Linux LVM中的PV物理卷
  20. 什么是网站的源代码?

热门文章

  1. 楚留香手游系统互通的服务器,楚留香手游互通服务器汇总 哪些服能一起玩
  2. MySQL面试题 | 附答案解析(十一)
  3. 图像识别-opencv
  4. Overleaf-LaTex表格制作
  5. GitHub开源的ImageAI 库:几行代码可实现目标对象识别
  6. 机器学习(4)特征预处理
  7. vue打印props的值_vue中props传值方法
  8. 基于平面几何精确且鲁棒的尺度恢复单目视觉里程计
  9. 在Ubuntu 14.04 64bit上安装Valgrind并检查内存泄露
  10. python threading模块多线程源码示例(一)