【C++】深入再学习const

文章探讨了C++中const关键字的三个层面限制:编译器层面禁止直接写操作,操作系统层面可通过指针绕过但会导致段错误,而物理层面(如ROM)则完全不可写。栈区和堆区的不同特性影响了const变量的可修改性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前情提要:
C++ 关键字const


[!NOTE]
昨天下班和实习生同事一起去吃饭,回出租屋就睡了,结果2点醒了到现在睡不着,就起来把这篇文章写了。刚写完,又困了,先睡了,早上起来再发表吧
2024年4月12日04点54分


三个层次

我们知道const,本质上是修饰一个变量的可写属性的。const 变量从某种角度上来说,就是限制了某个变量的可写属性。这又可以从三个层面来说,分别是编译器层面,操作系统层面和物理层面。也就是说,决定一个数据类型是否是常量有这三个维度的限制。每个维度的限制等级依次递增,首先来看编译器层面;


编译器层面

当我们定义一个const变量之后,编译器就会限定该变量是只读的,如果对其进行改动,编译就会不通过,如:

const int a = 1;
void func1() {
	a = 1;//虽然变量的值没变,但本质上是对变量进行写操作,但该变量是只读的,编译器不允许
}

请添加图片描述


操作系统层面

但其实编译器层面的语法检查可以通过指针的方式绕过去。我们知道,在CPU眼中,所谓变量常量不过都是一个地址的别名,而地址对于CPU来说基本都是可读可写的。所以通过指针的方式,我们可以绕过编译器的检查,如:

const int a = 1;
void func2() {
	*(long*)(&a) = 2;
}

通过这种方式,编译器不会在报错,但是运行程序时依然会发生段错误:
请添加图片描述

这是因为,虽然绕过了编译器。但是操作系统在为const分配内存时,是按照页为单位进行内存分配的,但一个变量是const时,操作系统MMU单元会为内存中这个页打上只读标签,这时如果再对该变量进行写操作,就会导致段错误。


破坏只读标签

我们说操作系统通过给页打上只读标签,那么这种标签可不可以破坏呢?答案是:可以。我们来看下面这个例子

int func3() {
  volatile const int c = 1;
  cout << c << endl;
  *(long*)(&c) = 2;
  cout << c << endl;
  return c;
}

请添加图片描述

在这例子中,我们通过在栈区创建了一个const变量c,再通过指针的方式修改它;再进行输出,可以看到,虽然程序最后发现了我们修改了const变量并通过terminated Aborted退出了;但是它依然无法阻止我们修改const类型的 c。
这是为什么呢?下面我们从物理层面分析这个问题;


物理设备层面

我们知道,内存有一种类型叫做ROM;即read-only Memmory。只读内存的写入方式和普通内存相比比较特俗,它是通过配合特定的设备和总线操作来进行的。而我们在全局区定义的const int a在程序初始化时就被程序放在了C++内存模型的数据段的常量区。这时MMU就会将该虚拟的常量区内存映射到真实的ROM内存上,这时这个变量就算天王老子来了,从软件层面上也无法进行写操作。何以证明呢?
比如

const int a = 1;
void func4() {
	*(long*)&a = 1;//不报错,但是段错误
	*(long*)(&a + 4) = 1;//不报错,但是段错误
	*(long*)(&a - 4) = 1;//不报错,但是段错误
}

上面这3部分内容每个都会触发段错误,就算因为全局变量const int a是放在数据段的常量区,而内存分配又是以页为单位的,所以那个页周围都是无法修改的变量。

但是在上文的func3()里,我们是在栈区开辟的空间,而栈区就不会被MMU映射到ROM上,而只是单纯的给该页打标签,没了物理上的限制,当我们通过指针的方式继续修改a时,操作系统是无法阻止我们的,最多事后发现错误来个Abort。
这是因为栈区不是一个ROM内存区域,所以是可以修改的。同理堆区也可以通过类似的方法验证,本文就不再赘述,留给读者自己探索吧,这里留下我在堆区验证的代码:

int func5() {
  const int* ptr = new int(5);
  cout << *ptr << endl;
  *(long*)(&ptr + 4) = 20;
  cout << *(long*)(&ptr + 4) << endl;
  *(long*)&ptr = 100;
  cout << *(long*)&ptr << endl;
  *(long*)(&ptr) = 20;
  cout << *ptr << endl;
  delete ptr;
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值