[C语言]

有关const的一个误区

[复制链接]
2641|18
手机看帖
扫描二维码
随时随地手机跟帖
lishutong|  楼主 | 2017-12-9 22:39 | 显示全部楼层 |阅读模式
本帖最后由 lishutong 于 2017-12-9 22:42 编辑

在学习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声明的常量是可以修改的。请看代码:

<pre class="highlight" style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 13px; padding: 9.5px; margin-bottom: 10px; line-height: 1.42857143; color: rgb(51, 51, 51); word-break: break-all; background-color: rgb(245, 245, 245); border: 1px solid rgb(204, 204, 204); border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px;"><code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; color: inherit; background-color: transparent; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; white-space: pre-wrap;">int main () {
    const int var = 0x1;

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

    return 0;
}</code></pre>

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

我们惊奇地发现:用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常量并不一定不可修改。


原创**:欢迎关注微信公众号 01ketang http://01ketang.cc

qrcode_for_01ketang.jpg


相关帖子

mailshichao| | 2017-12-10 11:21 | 显示全部楼层
const是从编译器的角度上定义变量不可修改,而变量数据保存在寄存器中或这flash中,从硬件的角度上看什么数据都可以修改,用指针的方式操作就相当于在硬件层面上的操作。

使用特权

评论回复
thinkabout4451| | 2017-12-10 18:06 | 显示全部楼层
mailshichao 发表于 2017-12-10 11:21
const是从编译器的角度上定义变量不可修改,而变量数据保存在寄存器中或这flash中,从硬件的角度上看什么数 ...

言简意赅,高!

使用特权

评论回复
linqing171| | 2017-12-10 22:04 | 显示全部楼层
const分配到flash是自己修改的链接脚本吧?
按c语言标准,const变量应该也是变量,默认是放ram的.

使用特权

评论回复
lishutong|  楼主 | 2017-12-10 22:28 | 显示全部楼层
linqing171 发表于 2017-12-10 22:04
const分配到flash是自己修改的链接脚本吧?
按c语言标准,const变量应该也是变量,默认是放ram的. ...

有这个标准吗?

使用特权

评论回复
delin17| | 2017-12-11 10:12 | 显示全部楼层
linqing171 发表于 2017-12-10 22:04
const分配到flash是自己修改的链接脚本吧?
按c语言标准,const变量应该也是变量,默认是放ram的. ...

const放在那个区,应该看具体编译器的link怎么去做的。并不一定是RAM区吧,很多单片栅编译器会放到ROM区吧。

使用特权

评论回复
憨厚诚实大叔| | 2017-12-11 11:56 | 显示全部楼层
从数据存储角度来说,就算是放到了FLASH,一样可以擦除修改

使用特权

评论回复
fengfeng的恒| | 2017-12-11 13:36 | 显示全部楼层
const定义的是一个变量,是一个只读变量,可不是什么常量。当然了,在单片机系统里,编译器一般把带有初值的只读变量放到表里,使用时查表读取而已,可参考一下生成的汇编代码。

使用特权

评论回复
877049204| | 2017-12-11 16:01 | 显示全部楼层
学习了!!

使用特权

评论回复
lishutong|  楼主 | 2017-12-11 21:53 | 显示全部楼层
fengfeng的恒 发表于 2017-12-11 13:36
const定义的是一个变量,是一个只读变量,可不是什么常量。当然了,在单片机系统里,编译器一般把带有初值 ...

很多教材和人都认为是常量
既然是只读,那为什么又是变量呢?

使用特权

评论回复
superupon| | 2017-12-12 09:14 | 显示全部楼层
在我看来。不考虑编译器的优化,从语言本身的角度而言,const只能算是一个修饰符,它是用来修饰某个变量的,
举个例子,const int a; 那么通过a这个变量来访问变量中存储的数值就只能是只读的,楼主的**写的很棒,非常有条理,
不过,通过另外一个指针来操作常量a所指向的内存区域,是肯定可以达到你的目的的。除非硬件有保护,否则,任何的内存区域都应该是可修改的。

使用特权

评论回复
guojin0273| | 2017-12-12 09:16 | 显示全部楼层
这不理解全面,CONST 如果有可能改动一般报警,
但是如果将报警条件,编译条件改动,就会报错了

使用特权

评论回复
fengfeng的恒| | 2017-12-12 09:24 | 显示全部楼层
lishutong 发表于 2017-12-11 21:53
很多教材和人都认为是常量
既然是只读,那为什么又是变量呢?

那是很多教材说错了。
只读变量是存在的。
常量与只读变量的区别:
常量的值在编译期间可以使用,但只读常量的值在编译期间不能使用。
举例:
1.
#define Max     100
int Array[Max];
2.
const int Max = 100;
int Array[Max];
第一点,在C语言里可以编译得过,因为Max是常量;第二点,编译不过,因为在编译期间不能使用变量的值。

使用特权

评论回复
丁弋宇| | 2017-12-12 12:35 | 显示全部楼层
我可以悄悄说这是抬杠吗?

使用特权

评论回复
lishutong|  楼主 | 2017-12-13 08:48 | 显示全部楼层
fengfeng的恒 发表于 2017-12-12 09:24
那是很多教材说错了。
只读变量是存在的。
常量与只读变量的区别:

第二点,在c99上已经可以支持了吧。这叫变长数组

使用特权

评论回复
lishutong|  楼主 | 2017-12-13 08:49 | 显示全部楼层
superupon 发表于 2017-12-12 09:14
在我看来。不考虑编译器的优化,从语言本身的角度而言,const只能算是一个修饰符,它是用来修饰某个变量的 ...

是的

使用特权

评论回复
lishutong|  楼主 | 2017-12-13 08:49 | 显示全部楼层
丁弋宇 发表于 2017-12-12 12:35
我可以悄悄说这是抬杠吗?

不是啊。这是探究深层,增进理解

使用特权

评论回复
killalljp| | 2017-12-14 16:12 | 显示全部楼层
丁弋宇 发表于 2017-12-12 12:35
我可以悄悄说这是抬杠吗?

同感!

使用特权

评论回复
zqx1000| | 2018-4-10 23:32 | 显示全部楼层
kankan

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:欢迎关注21ic公开课-自己动手从0到1写嵌入式操作系统,手把手教你写自己的RTOS。

15

主题

156

帖子

9

粉丝