const“常量”的值不const?
当你初学 C++ 时,老师告诉你可以使用 const 来定义一个常量。你尝试修改一个常量,得到了一个编译错误。于是你记下了这个知识点,而常量有什么用、编译器怎么保证常量不变,你不知道、也不关心。
后来你突发奇想,想试试“常量”真的不可修改吗,写出了这样的代码:
const int m = 111;
printf("%d\n", m);
int *p = (int *)&m;
*p = 222;
printf("%d\n", m);
编译通过了!但是为什么输出了两行
查看生成的汇编代码可以发现:聪明的编译器知道
printf("%d\n", 111);
printf("%d\n", 111);
怎么办呢?聪明的你使用 volatile 修饰符来告诉编译器不要优化对
const volatile int m = 111;
果然,得到了一行
你新建了一个文件,把它编译成一个动态链接库:
extern "C" const int value = 111;
extern "C" const int* get_point() {
return &value;
}
好了,现在的目标是通过 get_point() 返回的指针修改
const int *p = get_point();
printf("%d\n", *p);
int *p2 = (int *)p;
*p2 = 456;
printf("%d\n", *p);
段错误!事情变得有趣起来了。
聪明的你发现 .rodata 节,这个节在加载时会放进一个只读的内存页中,当程序尝试写入时便引发了段错误。
怎么办呢?聪明的你知道 Linux 有一个系统调用 mprotect ,它可以更改内存页的权限,只需要给
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
extern "C" const int* get_point();
void unlock_page_write(const char *maps) {
FILE *stream = fmemopen((void *)maps, strlen(maps), "r");
char *line = nullptr;
size_t len = 0;
while (getline(&line, &len, stream) != -1) {
unsigned long long begin_addr = 0;
unsigned long long end_addr = 0;
sscanf(line, "%llx-%llx", &begin_addr, &end_addr);
mprotect((void *)begin_addr, end_addr - begin_addr, PROT_READ | PROT_WRITE | PROT_EXEC);
}
}
int main() {
int fd = open("/proc/self/maps", O_RDONLY);
char buffer[8193];
ssize_t readed = read(fd, buffer, sizeof(buffer) - 1);
buffer[readed] = '\0';
unlock_page_write(buffer);
const int *p = get_point();
printf("%d\n", *p);
int *p2 = (int *)p;
*p2 = 222;
printf("%d\n", *p);
}
果然,得到了一行
不错,我们通过奇淫技巧绕过了 const 的保护……吗?
我们查阅C++标准就会发现:
Any attempt to modify a const object during its lifetime results in undefined behavior. (9.2.9.2)
修改const对象的任何尝试是未定义行为。 (翻译仅供参考)
也就是说,你之前做的一切和 (i++) + (++i)一样,没有任何保证。它不像
那么,const 到底有什么用?
也许对于 OIer 只要 AC 就行的哲学来说,const 确实没用。但是在真实项目开发中,你可能需要为一个一年前的代码添加功能,而一个 const 就会在编译时保证你不会修改不该修改的值,而不至于运行出现 bug 时 debug 半天。