首页 八字 正文

如何使一个运行中的程序使它出错的简单介绍

在C语言中,$const$是如何保证变量不被修改的?

我们可以想到两种方式:

如何使一个运行中的程序使它出错

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

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

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

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

如何使一个运行中的程序使它出错

编译,会收到一个 warning:

$ gcc -o const1 const1.cconst.c:Infunction‘main’:const.c:7:12: warning:initializationdiscards ‘const’ qualifier from pointer targettype[-Wdiscarded-qualifiers] int *p=&a;

忽略之,运行程序:

如何使一个运行中的程序使它出错

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

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

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

如何使一个运行中的程序使它出错

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

$ gcc -o const2 const2.cconst.c:Infunction‘main’:const.c:6:12: warning:initializationdiscards ‘const’ qualifier from pointer targettype[-Wdiscarded-qualifiers] int *p=&a;

忽略之,运行程序:

如何使一个运行中的程序使它出错

咦?怎么成功运行了,而且$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规定的,而是编译器厂商出于实际应用的考虑作出的选择。

欢迎交流。

PS:另外对于学习编程或者正在工作的朋友,如果你想更好的提升你的编程能力乃至转行,弯道超车,快人一步!笔者这里或许可以帮到你~

编程学习书籍分享:

如何使一个运行中的程序使它出错

编程学习视频分享:

如何使一个运行中的程序使它出错

分享(源码、项目实战视频、项目笔记,基础入门教程)

欢迎转行和学习编程的伙伴,利用更多的资料学习成长比自己琢磨更快哦!

点击下方【了解更多】可以免费获取更多学习资料帮助你学习成长哦~