C++Const变量的存储位置

发布时间 2023-07-20 20:43:07作者: 别杀那头猪

const变量/对象的存储位置

const局部变量

const局部基础变量和自定义变量都存储在栈上

struct diy_class{
    int a;
    int b;
    diy_class(int a, int b ) : a(a), b(b){
    }
};
int main()
{
    int b = 1; // 这个肯定在栈上
    const int a = 10;  // 比较a b两个变量的地址,看看a在哪里
    printf("address a = %p, address b = %p\n", &a, &b);
    const diy_class dd(1,2);
    printf("address of  diy_class = %p \n", &dd);
    // address a = 0x7ffd6926e44c, address b = 0x7ffd6926e448
	// address of  diy_class = 0x7ffd6926e450
}

对比3个变量的地址, 可知b在上。或者你也可以用GDB用 info locals 查看栈上的变量:

(gdb) # 打断点在printf("address a = %p, address b = %p\n", &a, &b);处
(gdb) info locals
b = 1
a = 10
dd = {a = -8016, b = 32767} # 这个栈变量还没有被初始化

const全局变量

再定义一个const全局基础变量,打印其地址

const int global_const_inited = 1; // 存储于只读常量区
int main()
{
    int b = 1; // 这个肯定在栈上
    const int a = 10;  // 比较a b两个变量的地址,看看a在哪里
    printf("address a = %p, address b = %p\n", &a, &b);
    const diy_class dd(1,2);
    printf("address of  diy_class = %p \n", &dd);
    // address a = 0x7ffd6926e44c, address b = 0x7ffd6926e448
	// address of  diy_class = 0x7ffd6926e450
    
    printf("address of global_const_inited = %p\n", &global_const_inited);
    // address of global_const_inited = 0x560d0df107f8
}

可以看到全局常量的地址明显不在栈上,那在哪? -- 常量数据区,可以用nm命令查看符号表验证:

$ nm const_storage_cpp | c++filt | grep global_const
00000000000007f8 r global_const_inited

其变量名前的符号为r,表示该变量存储在只读常量区。

接下来看看自定义变量:

const int global_const_inited = 1; // 只读常量区
const diy_class global_const_diy(1,2); 
int main()
{
    int b = 1; // 这个肯定在栈上
    const int a = 10;  // 比较a b两个变量的地址,看看a在哪里
    printf("address a = %p, address b = %p\n", &a, &b);
    const diy_class dd(1,2);
    printf("address of  diy_class = %p \n", &dd);
    
    printf("address of global_const_inited = %p\n", &global_const_inited);
    printf("address of global_const_diy = %p\n", &global_const_diy);
    // address of global_const_inited = 0x558b9d1dc888
    // address of global_const_diy = 0x558b9d3dd018
}

两个地址很相近,那么表示自定义对象的地址也在只读常量区吗? 我们使用nm命令验证以下:

$ nm const_storage_cpp | c++filt | grep global_const
0000000000201018 b global_const_diy
0000000000000888 r global_const_inited

发现并不是,对于只读自定义对象,存储在了BSS段。这与static自定义对象相同,它们都“存储”在了ELF文件的BSS段,并在main函数前完成初始化,详见我之前写的内容

不能修改const变量?

能修改const变量吗? --- 我们可以绕过编译器的限制,但是不能绕过操作系统的限制。要分情况看:

经过上文的探索,g++对const变量大致分为两种处理方式

  • 将变量存储在只读数据段
  • 将变量存储在栈和BSS段

操作系统在加载只读数据段时,会将该段设置为只读,无论进程以怎样的方式对它进行修改,都会触发缺页中断的读错误,操作系统在确定进程没有权限进行写时,会立刻向进程强制发送SIGV信号,然后进程就结束了。因此这种类型只读变量的不可变性是由操作系统和ELF格式决定的,无论如何都不能改变这种类型的只读变量。

然而BSS段和栈段是可读、可写的。只要我们通过了编译器的检查,我们可以使用某种方式在运行期对这种类型的只读变量进行修改。

具体可以看看下面的程序:

struct diy_class{
    int a;
    int b;
    diy_class(int a, int b ) : a(a), b(b){
    }
};
const int global_const_inited = 1; // 只读常量区
const diy_class global_const_diy(1,2);
int main()
{

    // 1. 编译器错误 !
    // global_const_diy.a = 10;  

    // 2. 绕过编译器,成功修改。C++种使用const_cast去除变量的只读属性
    diy_class* cc = const_cast<diy_class*>(&global_const_diy) ;
    cc->a = 10;
    printf("global_const_diy.a = %d\n", global_const_diy.a);

    // 3. 逃过编译器的检查,但没能逃过操作系统的检查. Segmentation fault!
    int* ee = const_cast<int*>(&global_const_inited); 
    *ee = 2;
    printf("global_const_inited = %d", global_const_inited);
}

注意在C++中,使用const_cast去除变量的只读属性