龙空技术网

CPU眼里的:常量(const)

阿布编程 4470

前言:

此刻我们对“定义常量的函数”大体比较重视,各位老铁们都想要知道一些“定义常量的函数”的相关资讯。那么小编也在网摘上汇集了一些有关“定义常量的函数””的相关内容,希望看官们能喜欢,各位老铁们快快来学习一下吧!

常量为什么不能被改写?有魔法吗?是的,有魔法

提出问题

什么是常量?常量就是:数值不能变化的变量。如果如此简单、易懂的定义,你都能挑出毛病,那可能就真的是在:哗众取宠了。提出问题的人,可能需要在自己身上找找原因了。

但你是否考虑过一个问题:是什么机制,在保证常量,不能改变这个初衷呢?要知道,市面上所有的内存条,都是可读、可写、可改的,想试图阻止 CPU 的写操作,谈何容易呀!

是程序员的自律?还是编译器的铜墙铁壁?就让我们的用 CPU 的视角,解读一个常量背后的故事。

代码分析

打开 Compiler Explorer,定义一个常量a,再定义一个变量b;为了防止编译器优化常量 a 的读写操作,我们特意在定义常量 a 的时候加上了关键字:volatile;至于 volatile 的工作原理,请查看“CPU眼里的volatile”。

接着,写一个函数func1,用来读取并返回常量a的值;然后,再写一个函数func2,用来读取并返回变量b的值:

老规矩,不要关心汇编指令的具体含义,我们只比较二者的差异,很显然除了a、b的内存地址不同外,两个函数的汇编指令完全相同!编译器并没有对变量和常量作任何的区分和特殊处理。

难道,我们又要得到一个耸人听闻的结论:变量与常量,本质上没有任何区别?且慢,我们再看看写操作,先给变量b赋值,编译通过,没有问题。

再给常量a,赋相同的值:1

inf func1(){    a = 1;    return 0;}

虽然我们并没有试图改变常量a的值,但我们的代码,还是会被编译器无情的拒绝!

看来,常量的含义不仅仅是:它的值不可改变,原来它是彻底的拒绝写操作呀。即便是你并不打算改变它的值。看来 const 关键字还真能保护常量的值,不会被重新写入。

为了解除编译器层面的禁止,我们需要为常量a稍微换个马甲,帮助它绕过编译器的检查:

如你所见,对常量a作一个向普通 int 类型的转换,这样就可以通过编译了。

再比较一下函数func1和函数func2的汇编指令,如你所见:除了a、b的内存地址不同外,它们的汇编指令是完全一致的!

好了,如此看来:常量a和变量b,在读、写操作上面,都是完全一致的。排除编译器在语法层面,对常量的保护,我们能否认为:常量与变量的本质是完全相同的呢?

到底是否相同,我们实际运行一下就知道了。写一个函数main,先调用一下函数func2,一切正常;然后我们再调用一下函数func1:

如你所见,在调用函数func1的时候程序出错,返回值:139意味着:段错误(segmentation fault)这是为什么呢?让我们分别打印:常量a和变量b的内存地址:

如你所见,虽然a、b是依次定义的,但是内存地址的距离,却超过了:8K 字节。它们显然不在同一个内存页里面,如“CPU眼里的程序运行”所说,程序运行前,代码中的全局变量、常量会被拷贝到数据段。只是这个数据段还会被细分成:只读数据段和可读、写数据段,因此,它们所在的内存页的读、写属性可能是不同的。

我们猜:变量b所在的内存页,在MMU映射表中的属性是:可读、可写的;常量a所在的内存页,在MMU映射表中属性是:只读的。因此,当我们强行对a进行:写操作时,就会触发CPU异常,导致程序崩溃!

因为,内存页的读写属性,不仅对常量a所在的内存有效,甚至对整个4KB的内存页,都是有效的。所以,即使试图对a周围的内存,进行写操作也是不被允许的:

夸张的说:现代操作系统和编程语言的实现,都离不开MMU这个好帮手。更多的MMU知识,还可以查看“CPU眼里的虚拟内存”。

总结

常量并不仅仅是:不能改变初值的变量,也是:不允许对其二次写入的变量。除此之外,它跟普通变量一样,也是某个内存地址的别名。编译器可以通过对代码的解读,阻止明显的、针对常量的写操作。但由于常量,跟变量一样,也只是内存地址的别名。所以程序员很容易通过指针、或类型转换的方式,逃过编译器的检查。真正保证常量不被写入的安全阀是:MMU,它能从物理上阻止对特定内存的读写。如果常量所在的内存页是不可读写的,例如:read only数据段,那么写操作会被MMU阻止,并产生CPU异常。

但如果常量所在的内存页是可读、写的,例如:函数内部定义的临时的“栈”常量,由于“堆栈”本身是可读、可写的,所以在逃过编译器检查后,“栈”常量也是可以顺利写入的。

热点问题

Q1:如果我通过cast强行转换成非const变量呢?

A1:效果是一样的,你或许可以绕过编译器的检查,但在真正作写操作的时候,会被MMU察觉到。

Q2:单片机,例如:STM32,没有MMU,它能阻止对常量的写操作吗?

A2:虽然单片机可能没有MMU,但它仍然有可能阻止程序对常量的写操作。因为,单片机在编译完程序后,往往会通过专门的设备把程序中的:常量、函数,烧写在:ROM上面。

由于ROM的写入过程比较特殊,需要配合特定的设备和总线操作;CPU不能通过常规的内存读、写指令(例如:MOV指令)来改写ROM上的信息,所以,代码对常量的写操作,即使可以顺利运行,但也很难真正改写ROM上的常量。

标签: #定义常量的函数