本节书摘来自华章计算机《编写高质量代码:改善c程序代码的125个建议》一书中的第1章,建议11,作者:马 伟 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
建议11:尽量使用const声明值不会改变的变量
从字面上理解,const是constant 的缩写,是恒定不变的意思,也翻译为常量、常数等。正是因为这一点,看到const关键字,很多人就认为被const 修饰的值是常量。其实,在C语言中,关键字const的功能非常强大,它不仅可以用来修饰普通变量、数组变量与指针变量等,还可以用来修饰函数的参数、返回值与函数本身。这些将会在后面的章节逐一详细讲解,本节只讨论使用const来修饰变量的情况。
对变量来说,const关键字可以限定一个变量的值不允许被改变,从而保护被修饰的东西,防止意外修改发生,这在一定程度上可以提高程序的安全性和可靠性。但是要正确地使用const变量,我们必须弄清以下几点。
1.const变量是只读变量,不是常量
提到const变量,总有人喜欢把它和常量混为一谈。其实const变量不是常量,准确地说它应该是只读的变量。为了让大家更好地区分这两个概念,我们来看下面的例子:
const int array_num=10;
int arr[array_num]={1,2,3,4,5,6,7,8,9};
在标准的ANSI C中,上面的这种写法是错误的,因为数组的大小应该是个常量,而const int array_num只是一个只读变量。虽然常量也是只读的、不可修改的(例如10、‘C’等),但只读变量却不等于常量,只读变量被编译器限定为不能够被修改。
实际上,根据编译过程及内存分配来看,这种用法本来应该是合理的,只是ANSI C对数组的规定限制了它。而在C++中,上面的这种写法确实是正确的。
2.确保变量的值不被修改
可以说,const关键字的最大作用就是确保变量赋初值之后,其值不会被任何程序修改。如下面的示例代码所示:
const int array_num=10;
array_num++; // 错误,不可以被修改
很显然,语句“array_num++”是错误的。同样,对于外部变量,const一样可以确保其变量的值不被修改,如下面的代码所示:
extern const int i; // 正确的引用
extern const int i="10"; // 错误,不可以被再次赋值
尽管如此,网上仍然流传着一种修改const变量的说法,如代码清单1-30所示。
代码清单1-30 const变量使用示例
#include <stdio.h>
int main(void)
{
const int i1=10;
int *p1=(int *)&i1;
int *p2=p1;
*p1=100;
printf("%d %d %d\n",i1,*p1,*p2);
printf("%p %p %p\n\n",&i1,p1,p2);
return 0;
}
有人说,这样const变量就能被修改了,但运行上面的程序,const变量的值根本没有改变,代码清单1-30的运行结果如图1-45所示。
图1-45 代码清单1-30的运行结果
在图1-45中,虽然“i1, p1,p2”所得的值不同,但“&i1,p1,p2”却是同一个地址,既然是同一地址,为什么输出不一样的值?
为了进一步查看其原因,我们在代码清单1-30中添加如下一条语句:
printf("%dn", ((int )0x0012FF74));
这样就可以直接输出地址所存的值了。得到的结果在我们的预料之中,为“100”。或许大家认为这样就改变了const变量的值。
其实,“int p1=(int )&i1;”表面上给了它们相同的指针,但是const变量的值是保存在数据段(只读)的,通过地址0x0012FF74查内存文件可得知其属于堆栈段。也就是说,虽然地址相同,但const变量读取的是数据段,而通过指针读取到的是堆栈段。
由此可见,const提供了一种保护机制,能在编译阶段阻止其变量的值被修改。但它并不能完全防止在程序的内部(甚至是外部)来修改这个值。也就是说,const变量是只读变量,既然是变量,那么就可以获取其地址,然后修改其值。因此,当汇编成二进制之后,这种保护机制就不复存在了。
3.节省空间,避免不必要的内存分配
使用const变量除了可以确保变量值不被修改之外,同时它还可以节省存储空间,避免不必要的内存分配。通常,编译器并不为普通const 只读变量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作,从而提高效率。