const_cast 引发只读数据区域写违例

发布于 2023-11-12 18:27:20 字数 2391 浏览 35 评论 0

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

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

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 这种试图绕过编译器的小聪明手段。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

情场扛把子

暂无简介

0 文章
0 评论
508 人气
更多

推荐作者

内心激荡

文章 0 评论 0

JSmiles

文章 0 评论 0

左秋

文章 0 评论 0

迪街小绵羊

文章 0 评论 0

瞳孔里扚悲伤

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文