这小段文章要理清楚的是,在C语言中,$const$是如何保证变量不被修改的?

我们可以想到两种方式:

第一种,由编译器来阻止修改$const$变量的语句,让这种程序不能通过编译;

第二种,由操作系统来阻止,即把$const$ 的内存地址访问权限标记为“只读”,一旦运行中的程序试图修改它,就会产生异常,终止进程。

上面想到的这两种方式,都能达到让某一变量的值不被修改的目的,那么究竟是哪一种呢?我们写两个例子来看一看。

先来看一个简单的例子,源文件const.c:

#include <stdio.h>
const int a=10;
int main()
{
int *p=&a;
printf("initial: %d\n",a);
*p=1;
printf("modified: %d\n",a);
return 0;
}

编译,会收到一个 warning:

$ gcc -o const1 const1.c
const.c: In function ‘main’:
const.c:7:12: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
int *p=&a;

忽略之,运行程序:

$ ./const1
initial: 10
Segmentation fault (core dumped)

运行出错了,报错是“segmentation fault”,即“段错误”,它是在提醒我们,程序中用错误的权限访问了内存某区域。这说明,操作系统把变量$a$加载到了一段只读内存区域之中,因此对该区域地址的写操作将引发异常,这是由操作系统的内存保护机制决定的。

也就是说,在这段程序里,$const$的只读属性是由操作系统来实现的,而不是由编译器来实现的(编译器只抛出了warning,并没有阻止编译通过)。

这对吗?不完全对,我们来看另一个例子,源文件const2.c:

#include <stdio.h>
int main()
{
const int a=10;
int *p=&a;
printf("initial: %d\n",a);
*p=1;
printf("modified: %d\n",a);
return 0;
}

编译,还是收到同样的warning:

$ gcc -o const2 const2.c
const.c: In function ‘main’:
const.c:6:12: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
int *p=&a;

忽略之,运行程序:

./const2
initial: 10
modified: 1

咦?怎么成功运行了,而且$a$的值还被顺利修改了?

结合以上两个例子,我们可以得出以下结论:

$const$只是C语言中的一种对变量的修饰符,例子中的$a$,与其说是“常量”,不如说是“不打算修改的变量”。它只是语法上的一种声明,它的作用就是告诉编译器“我不想修改它”,因此编译器会从语法上检查程序中是否有修改它的语句(例如“a=1;”),一旦发现这种“违背初衷”的语句,就会报错阻止你。

然而,编译器所阻止的仅仅是对$a$这个符号对应值的修改而已,却并不阻止对这个地址的值的修改,源文件“const2.c”之所以能顺利通过编译且正常运行,就是因为它利用一个名字不叫$a$的指针指向它,从而绕过了编译器的语法检查。

打个比方,周树人的笔名叫鲁迅,警察只知道要抓鲁迅,这时候他就可以用一句“你们抓鲁迅跟我周树人有什么关系?”来骗过他们。

从这个角度来说,$const$的作用是靠编译器仅仅从语法检查来实现的,因此存在运行时的漏洞。

那么为什么“const1.c”就不能正常运行呢?

仔细看这两个源程序,区别仅仅在于,在const1.c中,$a$被声明为全局变量,而在const2.c中,它被声明为main函数中的一个局部变量。全局变量与局部变量的区别在于,前者会在程序开始运行之前就被加载,加载后会一直留在内存中,且加载的位置在数据区,直到程序退出;后者只有在运行到它时才会被加载,且加载的位置是运行时的栈帧,一旦超出作用于就会被回收。

因此,编译器会对被声明为全局变量的$const\ int\ a$进行优化,把它放到只读内存区内,这一内存区的权限是$read\ only$,权限信息由操作系统所维护的段表来保存,程序每访问某地址时,操作系统都会检测其访问权限是否合法。const2.c中企图用“写”的方式来访问“只读”的段,自然会报出“segment fault"的错了。

从这个角度来说,当$a$是全局变量时,编译器把原本只是“不打算修改的变量”优化成了“真正的常量”,然后交给操作系统去维持其不变属性。

综上所述,C的初衷只是让编译器去保证$const$的不变属性,这一属性有漏洞(可以用指针去骗过编译器修改它),所以当const修饰的对象是全局变量时(全局变量很重要,因为很多源文件都要访问它,牵一发而动全身,所以不应轻易更改),编译器知道自己的能力有限,只能管得了编译,管不了运行时如何,所以优化了语句把它编程真正的常量,让操作系统的内存保护功能来履行这一职责。

这一优化,并不是C规定的,而是编译器厂商出于实际应用的考虑作出的选择。

欢迎交流。

C语言const是如何保证变量不被修改的?的更多相关文章

  1. c++ , const对象中的变量不能被修改

    const对象中的变量不能被修改,即使const对象中的函数也不能修改该对象中的变量值 #include <iostream> using namespace std; //------- ...

  2. 新手小白入门C语言第四章:变量与常量

    C 变量 变量其实只不过是程序可操作的存储区的名称. C 中每个变量都有特定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上. 变量的名称可以由字母.数字和 ...

  3. 《OOC》笔记(1)——C语言const、static和extern的用法

    <OOC>笔记(1)——C语言const.static和extern的用法 C语言中const关键字用法不少,我只喜欢两种用法.一是用于修饰函数形参,二是用于修饰全局变量和局部变量. 用c ...

  4. C语言的数据类型及其对应变量

    声明,定义和初始化 声明标识符iden是告诉编译器"有这么一个变量var,具体var里是什么,你自己去看".声明只需要标识符的类型和标识符名字,C语言的任何标识符在使用前都需要声明 ...

  5. 李洪强iOS开之【零基础学习iOS开发】【02-C语言】04-常量、变量

    在我们使用计算机的过程中,会接触到各种各样的数据,有文档数据.图片数据.视频数据,还有聊QQ时产生的文字数据.用迅雷下载的文件数据等.这讲我们就来介绍C语言中数据的处理. 一.数据的存储 1.数据类型 ...

  6. 【转】话说C语言const用法

    原文:话说C语言const用法 const在C语言中算是一个比较新的描述符,我们称之为常量修饰符,意即其所修饰的对象为常量(immutable). 我们来分情况看语法上它该如何被使用. 1.函数体内修 ...

  7. C语言学习及应用笔记之三:C语言const关键字及其使用

    在C语言程序中,const关键字也是经常会用到的一个关键字,那么使用const关键字的目的是什么呢?事实上,在程序中使用const关键字的主要目的就是为了向使用者传递设计者的一些意图. 事实上,无论我 ...

  8. c语言const和c++const

    1.常量 常量是指值不能被改变的量,又叫做字面值 1.1常量分类 1)字符常量:'a', 'A', '*'. 2)字符串常量:"helloworld","ilovechi ...

  9. GO语言的基本语法之变量,常量,条件语句,循环语句

    GO语言的基本语法之变量,常量,条件语句,循环语句 作为慕课网得笔记自己看 定义变量: 使用var关键字 var a, b, C bool var s1, s2 string = "hell ...

随机推荐

  1. 手机 wifi 已连接,不可上网 bug

    手机 wifi 已连接,不可上网 bug 同一个 Wi-Fi,电脑却可以? 注意事项 Mac 共享热点支持有线连接.蓝牙连接的网络进行共享. 如果你的 Mac 本身是通过 wifi 来连接上网的,那就 ...

  2. ORM & sequelize

    ORM Object Relational Mapping 对象关系映射 Table => Object, 简化 SQL 查询命令的编写 https://en.wikipedia.org/wik ...

  3. position: absolute; not work

    position: absolute; not work https://stackoverflow.com/questions/11928294/css-position-absolute-with ...

  4. TCP编程详解

    目录 数据包格式 建立连接(三次握手) 数据传输 断开连接(四次挥手) 基础 客户端流程 编码 TCP服务端流程 TCP服务端编码 参考文献 TCP把连接作为最基本的对象,每一条TCP连接都有两个端点 ...

  5. django学习-12.访问不同url/接口地址实现对指定数据的增删改查功能

    1.前言 通过前面博客[django学习-10.django连接mysql数据库和创建数据表]里的操作,我们已经成功在数据库[hongjingsheng_project]里创建了一张数据表[hello ...

  6. Elasticsearch---DSL搜索实践

    Domain Specific Language 特定领域语言,基于JSON格式的数据查询,查询更灵活,有利于复杂查询 一.普通url路径参数搜索 数据准备 1.建立名字为 shop 的索引 2.手动 ...

  7. servlet内置对象(传递数据)

    一个servlet向另一个servlet发送数据,可以将数据放置在一个容器中(io.数据库.servlet的内置对象),servlet的内置对象成本最小. 一共有三个内置对象. 名字 类型 reque ...

  8. HarmonyOS三方件开发指南(13)-SwipeLayout侧滑删除

    鸿蒙入门指南,小白速来!0基础学习路线分享,高效学习方法,重点答疑解惑--->[课程入口] 目录:1. SwipeLayout组件功能介绍2. SwipeLayout使用方法3. SwipeLa ...

  9. Django-用户权限,用户角色使用指南

    RBAC(Role-Based Access Control,基于角色的访问控制)就是用户通过角色与权限进行关联.简单地说,一个用户拥有若干角色,每一个角色拥有若干权限.这样,就构造成"用户 ...

  10. Go语言学习之路-11-方法与接口

    目录 编程方式 go语言对象方法 自定义类型和方法 接收器: 方法作用的目标(类型和方法的绑定) go面向对象总结 方法的继承 go语言接口 为什么要用接口 接口的定义 接口的作用总结 接口的嵌套 空 ...