编程语言应用

首页 » 常识 » 诊断 » c语言深度解剖只读变量常量字面量
TUhjnbcbe - 2025/7/16 11:56:00
北京中科中医院 https://jbk.39.net/yiyuanfengcai/ys_bjzkbdfyy/792/
写在前面

本篇主题的缘起,是因为一个计算机专业的大学生在和我讨论c语言问题时,说const常量如何如何,我说变量被const修饰了,还是变量,不是“常量”。他给了我一个截图:

他说大模型都是这样回答的,变量a被const修饰后,就是常量了。我一点都不意外,大模型在技术领域的错误百出我都已经麻木了。甚至,我特意限定在c语言里,常量和字面量的异同,也仍然给出了错误的解答:

首先声明,我是大模型的坚定拥护者,AIGC必将实现更接近人类思考方式的新的编程形式,但是现阶段,还是非常稚嫩的,所以出现错误是需要宽容给的。

后来又和一些同学在交流时,我发现一些基础的、但容易混淆的概念还是很需要理清的,在网上我好像也没找到太全面的解答,就决定自己尝试来和大家一起梳理把这几个概念放在一起,对比剖析下。我这篇只是引子,能抛砖引玉就达到目的了。

解剖:只读变量

我们先来看下只读变量。所谓只读变量,就是用const定义的变量,希望在程序运行期间该变量的值不被修改,但是它却和普通变量一样仍然被存储在“可读写”的内存空间中,这就造成了可以用一些所谓的“非法”手段(我更喜欢说成hack手法)仍然可以修改其值,甚至直接通过可读写指针就可以修改,举例如下:

constintx=;int*hacker=x;intcracker[1];*hacker=;cracker[1]=;

变量x用const修饰了,但是无论是通过hacker指针,还是cracker的数组越界,都可以篡改x的值,因为x的值存储在可读写区域。我用的是CLionIDE,只要是c编译器都可以编译通过(文件后缀也得是.c),如果用c++会报错,因为在c++中,const变量就是作为常量存储,放在只读区域的。

下面是编译通过的代码截图:

那么有没有办法避免吗?有,但不是今天的主题,为了不跑题太远,可以搜索“段誉和语言”的其他文章,都有讲到。

解剖:常量

而对于c语言的常量,准确的定义到底是什么?反正我感觉是众说纷纭,各种说法都有,但是有一点是确定的,就是常量在程序运行期间其值无法被修改,因为存储在存储在内存的“只读”区域,程序运行时,其数据是受操作系统保护的。

常量是相对于变量而言的,既然变量是由变量的名称、变量的值、变量的内存空间、变量的类型四部分组成,那么常量是不是这样呢?我们通过对比的方式来看下,就一目了然了。

变量的定义如下:

ifloatpi=3.14;

变量名是pi,变量类型是float,变量的值是3.14,变量的内存空间,在编译时就被分配内存,运行时拥有自己的内存空间。

那么常量的名称、常量的值、常量的类型、常量的内存空间等如何体现的呢?

我们用一个最常见的方式来定义如下:

//宏常量

#definePI3.14

这是宏常量(又叫符号常量)的定义形式,常量名是PI,常量的值是3.14,也有自己的内存空间,只是放在了只读区域,至于常量的类型,是隐含起来的,通过数值本身的形态编译器自动识别的。这个常量的类型,一定要小心了,初学者都会犯错,类型是double,不是float!如果想要定义一个float型宏常量,可以这样做:

#definePI3.14F

可以看出,宏常量和一般变量的区别,类型是隐含的,没有明确的显示出来。宏常量只是常量的一种表示形式,其他的比如说还有枚举常量。

枚举类型使用起来简单,但是枚举类型略微有点特殊。因为枚举类型定义的变量是枚举变量,但是用来给枚举变量赋值的却是枚举常量。举例如下:

#includestdio.htypedefenum_color{RED=1,GREEN,LUE}COLOR;intmain(){COLORimgColor=RED;return0;}

这段示例代码中,imgColor是枚举类型COLOR的变量,RED、GREEN、BLUE是枚举类型COLOR的枚举常量。

既然说RED等是常量,那就肯定是分配在只读内存区域的,事实上也是这样,可以测试一下:

可以看到,RED是不可修改的,证明了枚举常量是真正的常量。

除了宏常量、枚举常量外,还有一种常量的表现形式,就是是常量表达式。比如:

intx=2*3;

赋值符号“=”右边的2*3,就是常量表达式,在编译时要计算出来一个确定的值,然后作为常量存储起来,并复制到变量x的内存空间中去。

解剖:字面量

那么常量表达式2*3中,2和3又是什么呢?

2就是字面意义上的2,3就是字面意义上的3,什么叫字面意义?通俗的说就是本意。比如你说我2,肯定不是说我是数字2,而是说我是愣头青,那么2这个时候就是比喻义(或者引申义?)

字面量的英文literal,翻译成中文就是“字面意思,本来的意义”,因此,一个具体的数字、字符、字符串,就是字面量。比如:

intx=2;inty=1+3;charc=‘c’;char*s=“hello”;#definePI3.14

这段例子中,字面量2赋值给了变量x,字面量1和3组成了常量表达式1+3,编译时编译器会直接计算出4,作为常量(或者字面量)赋值给y,字面量’c’直接赋值给字符变量c,字面量”hello”直接赋值给字符指针s,字面量3.14是常量PI的值。

这里顺便提一下,编译时编译器会给每个字面量分配一个只读空间存储这个值,也会给变量在可读写的内存区域分配空间,然后再把对应的字面量复制到变量的内存空间。

另外,在c语言中,只有字符串字面量(称字符串常量也可以,实际上你想怎么称呼都可以),没有字符串变量。

在顺带说一下,字符串字面量和字符数组是2个概念,比如:

chars1[]={‘h’,’e’,’l’,’l’,’o’};char*s2=”hello”;chars3[]={‘h’,’e’,’l’,’l’,’o’,’\0’};

s1是含有5个字母的字符数组,s2是指向字符串字面量”hello”的指针,这个字符串字面量是6个字符!最后一个结束字符’\0’是不可见字符.s3是含有6个字符的字符数组。s1和s2是两个完全不同的对象,s3可以和s2看作是等价的。

因此,字面量和常量是最容易混淆的,两者非常相似。都被分配在“只读”的内存区域,无法修改,受操作系统保护的。

他们较为明显的区别,就在于字面量是所有的具体的数值的统称,并且给常量和变量进行初始化或赋值。

而常量则有具体的名字,非常易于理解,使程序更具有可读性、可维护性,在整个程序运行期间可以一直被重复使用,而字面量是没有名字的,只有具体的值,具有“匿名”的特点。

总结

我们总结一下,const修饰的变量,在c语言中,对数据能起到一定程度的保护作用,但是无法完全避免数组越界、可读写指针等手段对数据的篡改,因为是存储在可读写区域的,运行时没有受到操作系统的保护。

常量是相对于变量而言,和变量一样,包含常量名、常量值、常量类型、常量内存空间四个组成部分,存储在只读区域,常量值由字面量赋值,常量类型由编译器对字面量本身的数值形态进行识别。

字面量就是所有具体数值的统称,比如数字、字符、字符串。他们不同于变量、常量,没有字面量名字,本身数值形态即是字面量值,又是字面量类型,分配在只读区域,不能修改。一般用来给变量或常量初始化、或赋值(初始化和赋值是不同的行为,不能混为一谈)。

因为字面量没有名字,所以它们具有“匿名”的特性。

我们接着来剖析什么是“组合字面量”?什么是“匿名数据”?

原创真心不易,特别是技术文章,希望大家点点赞。

段誉,年1月,写于合肥。#优质作者榜#
1
查看完整版本: c语言深度解剖只读变量常量字面量