C和C++的区别(3) const增强

【const在C语言和C++中的相同点】
首先看C语言中const修饰变量的几种用法:

//常整型
const int c = 10;
int const d = 20;

//常量指针:指针指向的内存数据不能被修改,但是本身可以修改
const int* e;
int const* f;

//指针常量:指针指向的内存数据可以被修改,但是本身不能被修改
int* const g = NULL;

//指针和它指向的内存空间都不能被修改
const int* const h = NULL;

上面的用法在C++依然是一样的,没有区别。
C和C++中用const修饰的变量初始化完成后,值不能被修改,不能再作为左值。
例如:

#include <stdio.h>

int main()
{
	const int a = 10;
	a = 20;//报错
}

上述代码不管在C还是在C++编译环境中都会报错。

C和C++中,
const全局变量,常量区,一旦初始化不可直接或间接修改
const局部变量,栈区,可间接修改,不可直接修改,可以间接修改

const全局变量

//一旦初始化,不能直接或间接修改
const int a = 1;
void test1()
{
	//不能直接修改
	a = 2;

	//int* pa = &a;有const的警告
	int* pa = (int*)&a; 
	//间接修改也不行
	* pa = 3;
}

const局部变量

void test2()
{
	const int b = 1;
	b = 2;//不能直接修改
	int* pb = (int*)&b;
	*pb = 2;//可间接修改
}

const全局变量为什么不能间接修改?
因为const全局变量在常量区,受到保护,不允许修改

【const在C语言和C++中的对比一】
C语言中const是定义了一个const变量,该变量只具备读的功能,不具备写的功能。
而C++中const是定义了一个常量,可以直接用于数组的维度。

const int a = 5;
int array[a];//在C语言中编译错误,因为a是一个变量
int array[a];//在C++中正确,因为a是一个常量

但也不是说C++中只要是const变量就可以用在数组维度里,比如:

int b = 5;
const int a = b;
int array[a];//报错

原因是,在C++中,const被当作常量的前提是它的初始化必须要是一个字面值,当我们用另一个变量来初始化时就会退化为常变量,编译规则就和C中一样了。这里的a就退化成了常变量。

【const在C语言和C++中的对比二】
在C++中,const修饰的变量必须要初始化,否则编译不通过;
而在C中,const修饰的变量是可以不用初始化的,但如果const修饰的变量不初始化,我们也就没有机会重新给它赋值,一般还是建议在定义的时候初始化。
在这里插入图片描述
在这里插入图片描述

【const在C语言和C++中的对比三】
在C语言中,const变量依然可以通过指针对其值进行改变,而在C++中修补了该漏洞。

int main()
{
    const int a = 10;
    int* p = NULL;
    p = (int*)&a;
    *p = 20;
    printf("%d\n", a);
}

上述代码,在C程序中结果为20,在C++程序中结果为10。
可见在C语言中,const变量没有真正变成不能改变的常量,依然可以通过指针对其值进行改变。
而在C++中,const变量无法通过指针对其值进行改变。

#include <stdio.h>

int main()
{
	const int a  = 10;
	int* p = (int*)&a;
	*p = 20;

	printf("%d %d %d", a, *p, *(&a));
}

创建一个名为main.c的文件,运行结果如下:
在这里插入图片描述
而用上面的代码创建一个main.cpp的文件,运行的结果如下:
在这里插入图片描述
可以看到虽然*p的值被修改了,但和a和*(&a)的值都没有被改变。

看到这里不免产生疑问:为何*p 会得到20,而直接输出a却是10,*p不就是指向a的吗?

有的人解释说,printf打印的a是在寄存器上获取的,而内存中a已经被修改为20.链接
在这里插入图片描述

我设计如下的测试程序来看看是不是因为printf的原因才导致a实际已经是20了却打印成10:

#include <iostream>

using namespace std;

int main()
{
    const int a = 10;
    int b = 0;
    int* p = NULL;
    p = (int*)&a;
    *p = 20;
    printf("%d\n", a);
    printf("%d\n", *p);
    b = a;
    printf("%d\n", b);
    printf("%p %p\n",&a,p);
    int* p2 = (int*)&a; //这里有点走火入魔了,想换个指针再指向a试试
    printf("%d\n", *p2);
}

上面的程序,我重新定义了非const变量b,把a赋值给b,这样如果a已经是20的话,b应该也会是20,结果如下:
在这里插入图片描述
实验证明,b的值是10,这样来看,a依然是10没有改变,但奇怪的是,我把a的地址也打印出来了,与p的指向是一致的,可以a明明是10,怎么通过指针打出来是20呢?

上面这段程序太繁杂了,我们回到前面的程序,打断点后查看反汇编代码:
源码:

#include <stdio.h>

int main()
{
	const int a  = 10;
	int* p = (int*)&a;
	*p = 20;

	printf("%d %d %d", a, *(&a), *p);
}

反汇编:

#include <stdio.h>

int main()
{
00D71860  push        ebp  
00D71861  mov         ebp,esp  
00D71863  sub         esp,0DCh  
00D71869  push        ebx  
00D7186A  push        esi  
00D7186B  push        edi  
00D7186C  lea         edi,[ebp-0DCh]  
00D71872  mov         ecx,37h  
00D71877  mov         eax,0CCCCCCCCh  
00D7187C  rep stos    dword ptr es:[edi]  
00D7187E  mov         eax,dword ptr [__security_cookie (0D7A004h)]  
00D71883  xor         eax,ebp  
00D71885  mov         dword ptr [ebp-4],eax  
00D71888  mov         ecx,offset _4F4DB909_main@cpp (0D7C003h)  
00D7188D  call        @__CheckForDebuggerJustMyCode@4 (0D71316h)  
	const int a = 10;
00D71892  mov         dword ptr [a],0Ah  
	int* p = (int*)&a;
00D71899  lea         eax,[a]  
00D7189C  mov         dword ptr [p],eax  
	*p = 20;
00D7189F  mov         eax,dword ptr [p]  
00D718A2  mov         dword ptr [eax],14h  

	printf("%d %d %d", a,  *(&a), *p);
00D718A8  mov         eax,dword ptr [p]   ------>打印是先打*p
00D718AB  mov         ecx,dword ptr [eax]  
00D718AD  push        ecx  
00D718AE  push        0Ah   ------>然后打印*(&a)发现直接是0Ah
00D718B0  push        0Ah   ------>然后打印a发现也是直接0Ah
00D718B2  push        offset string "%d %d %d" (0D77B30h)  
00D718B7  call        _printf (0D710CDh)  
00D718BC  add         esp,10h  
}

上面的打印环节的汇编代码看到了,a 和 *(&a)发现直接打印10,根本没有去对应内存获取。

而造成这种结果的原因,是因为在C和C++中对const的编译方式不同。
在C中,const就是当作一个变量来编译生成指令;而在C++中,所有出现const常量名字的地方,都被其初始值替换了,如上面的printf("%d %d %d", a, *p, *(&a));这行代码在C++中实际是这样执行的 printf("%d %d %d", 10, *p, *(&10));,但实际上p指向的这块内存的值已经被修改了,所以*p输出的结果是20。

根据这套理解,再看前面的程序就好理解了,主要分辨哪些情况a会直接换成10,哪些情况a没有换成10,结合汇编得出:

#include <iostream>

using namespace std;

int main()
{
    const int a = 10;
    int b = 0;
    int* p = NULL;
    p = (int*)&a;
    *p = 20;
    printf("%d\n", a);   //---> a换成10 printf("%d\n", 10);
    printf("%d\n", *p);  //--->printf("%d\n", *p);
    b = a;  //--->b = 10;
    printf("%d\n", b);  
    printf("%p %p\n",&a,p);  //printf("%p %p\n",&a,p);  这里注意&a由于对a进行取地址操作所以a没有直接换成10 
    int* p2 = (int*)&a;
    printf("%d\n", *p2);
}

该问题总结:
C语言中的常量不能直接修改,但是能通过指针访问间接修改。

C语言中会为const常量单独分配内存,只要修改了内存中的空间,就会影响常量本身。

C++中,const表示一个真正的常量。

C++中在const修饰的常量编译期间,值就已经确定下来了。

C++编译器对const做了加强,当C++编译器扫描到const常量声明时,它不会像C语言一样为直接为const单独分配内存。 定义int const a = 10;之后,C++编译器会将a放到一个符号表里面,符号表的存在形式为key-value的形式。我们定义常量a=10,key就是a,value就是10。这个值就是锁死了,不能变的。 const变量在预处理时处理,编译器只对其值读取一次。 用到a的时候,编译器根本不会去进行内存空间的读取,会从符号表里面把a的值拿出来。

这就是C++的 常量折叠(constant folding) ,即将const常量放在符号表中,而并不给其分配内存。编译器直接进行替换优化。除非需要用到a的存储空间的时候,编译器迫不得已才会分配一个空间给a,但之后a的值仍旧从符号表中读取,不管a的存储空间中的值如何变化,都不会对常量a产生影响。
但是在C语言中却不是这样,C没有constantfolding的概念。 用const定义一个常量的时候,编译器会直接开辟一个内存空间存放该常量,不会进行优化。

如果在C++中不想要这种编译优化,想让读取a的值的时候,不从符号表中去读取,而是从内存中去取。 可以用这样的定义方法:

const volatile int  a = 10;

当使用&操作符取局部const常量的地址时,会分配临时的内存空间,存储在堆栈中,可通过指针修改临时空间的值;所以,编译器会为a单独的开辟一块内存空间,p = (int )&a;然后这个内存空间赋给指针p,就是p指向这个内存空间。通过p去间接修改的值,是这个新开辟的内存空间的值,而不是符号表中a的值,我们让*p=20、30、100…修改的都是新开辟内存空间中的值。不会影响到常量本身的值。

【const在C语言和C++中的对比四】
有一种情况C++中也可以通过指针改变const变量:

#include <stdio.h>

int main()
{
	int b = 10;
	const int a = b;
	int* p = (int*)&a;
	*p = 20;
	printf("%d %d %d\n",a,*p,*(&a));
}

在C++中,这种情况即使a是const变量,也还是被指针改变了值。
在这里插入图片描述
原因还是,在C++中,const被当作常量的前提是它的初始化必须要是一个字面值,当我们用另一个变量来初始化时就会退化为常变量,编译规则就和C中一样了。这里的a就退化成了常变量。

【const在C语言和C++中的对比五】
C++中,编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。在C语言中,const变量无论何种情况都是分配了内存的。

C语言的const修饰的变量都有空间,全局的在常量区,局部的在栈区;
C++语言的const修饰的变量有时有空间,有时没有(没有的情况是发生常量折叠,且没有对变量进行取地址操作);

为什么对const变量进行取地址操作就得要分配空间了?
因为当我们对这个变量取地址的时候,由于原来没有空间,就没有地址,现在需要取地址,所以才被迫分配一块空间。

【const在C语言和C++中的对比六】
在C语言中,只要是全局变量,不论有没有被const修饰,都是默认拥有外部链接属性的,也就是说这个全局变量不仅限于在当前文件下使用,只要我们在其他文件中,加上extern的声明,也是可以使用的。

//定义的文件
const int a = 10;
//另外一个文件声明
extern const int a;

C++中被const修饰的普通全局变量是内部链接属性的。
也就是说当我们在一个文件中定义了一个如下的全局变量

const int a = 10;//定义全局变量
int main() {
	return 0;
}

我们在另外一个文件中,使用extern来声明,也是不可以的。

//另外一个文件
extern const int a;//在另外的文件中声明

上面这种做法是不可以的,C++中被const修饰的全局变量默认是内部链接属性,不能直接在另外的文件中使用,如果想要在另外的文件中使用,就需要在定义该全局的变量的文件中用extern来修饰

//定义的文件
extern const int a = 10;
//另外一个文件声明
extern const int a;

参考鸣谢:
C和C++中const的区别

https://blog.csdn.net/weixin_42562387/article/details/114017352

https://blog.csdn.net/Eric_Jo/article/details/4138548

https://blog.csdn.net/Always__/article/details/51103939

http://www.45fan.com/article.php?aid=1yH9wVrhTv1rGvpO 【优质】

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吾爱技术圈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值