有关const的一个误区

2017-12-09 00:00:00 +0000 李述铜

在学习C语言时,无数本教材都告诉我们:用const 类型 标志符; 这种方式是定义一个常量,也就意味着不可以修改。但真的只是这样吗?例如:

int main () {
    const int var = 0x1;

    var = 2;

    printf("%d\n", var);
    return 0;
}

你可能会说: var是不可修改的。从表面上看,似乎是这样,现在来编译一下,编译器提示以下的错误。

app.c(4): error:  #137: expression must be a modifiable lvalue

编译器提示var必须是可以修改(modifiable)的量。var真的不可以修改吗?

一个可以修改的const原因

现在,我们对代码进行一下修改。通过这个修改你可以发现const声明的常量是可以修改的。请看代码:

int main () {
    const int var = 0x1;

    int * pVar = (int *)&var;
    *pVar = 0x2;

    return 0;
}

这段代码,如果我们把它放在Keil + STM32平台上跑,并且将var和pVar加入变量watch窗口中,会发现下面的效果:

const修改

我们惊奇地发现:用const声明的var值,可以通过指针pVar间接修改!。为什么会这样?

const常量可以修改的原因

分析上面的代码可以发现,原因非常简单:

在Keil + STM32平台上,用const声明的var实际上是在堆栈空间中分配的,也就是说,从物理上来看,var是分配在内存中。而此时硬件上也没有开启任何内存检查的机制,所以,我们可以间接地通过PVar指针对这个变量进行修改。

既然如此,那用const声明还有什么用?

const常量的优点

可以从以下几个方面来理解const常量的优势。

可以将常量放置到Flash空间

通过将const声明的常量放置到函数外部作为全局,链接器会自动将该常量分配到Flash中。这样可以节省内存开销。

观察上图可以发现,var被分配在了地址0x080000388,而此空间正好在Flash区域。

现在的编译器够智能,看到了const就知道我们不想对该变量进行任何修改,自动分配到不可随意写的Flash中。既有效地保护了该常量,又节省内存空间,一举两得。

语法检查机制

对我们想要只读的常量加上const,可以让编译器在编译过程中,发现代码中任何试图”违反规则,企图修改”该变量的任何代码。

例如,本文最开始的编译错误。甚至于在写代码时,好的编辑器都能实时帮我们检查这种错误。

更好的可读性

此外,由于加const,其他的程序员在阅读你的代码时,能够直接了当知道这个常量是不应修改的。特别是在传递一些函数的参数时,例如:

void process (const char * var) {
    // 一些代码    
}

我们不需要再加注释说明var是不是会在process中修改。其他人只需要看到这个函数声明,就知道传入的指针不会被间接使用修改所指向的变量。

总结

总结以上内容,我们对const有了一个新的了解:const声明的常量并不一定是不可修改,其主要功能是注明其不应当被修改。之后编辑器、编译器、链接器可以自行根据该声明,做出相应的处理,从而实现以下的几个特性。

  • 根据硬件存储布局自动安排合理的位置
  • 语法检查,有效避免写代码时出错
  • 更好的可读性

注:以上测试均在Keil + STM32平台上实现。在其它平台上,const常量可能确实是不可修改的。本文的结果也指出, const常量并不一定不可修改。

推荐课程

  • 自己动手写嵌入式操作系统

    循序渐进,40余次迭代,写出不到2000行代码的嵌入式操作系统

  • 手把手教你学用嵌入式操作系统

    从实用角度出发,教你如何使用嵌入式操作系统

  • 深入理解ARM调试原理

    深入理解Jlink/Ulink等仿真调试工具背后工作的秘密

  • 轻松掌握Git & GitHub

    掌握当今最流行的版本管理工具,帮你找回过去任意时间的文件版本

  • 自己动手学用Keil(MDK)

    轻松掌握最主流的ARM开发工具Keil(MDK)