Summary
1)const修饰的变量是只读的,本质还是变量
。(不论值能否改变)
2)分配空间
:
const修饰的局部变量在栈上分配空间
。
const修饰的全局变量在全局数据区(或只读存储区)分配空间
。(这两点印证了const修饰的仍然是变量,因为在内存中依然会分配空间
)
3)const只在编译期有用,在运行期无用。(表示:const修饰的变量,在编译的时候不能放在赋值符号左侧
,但是在运行期,就可以通过指针改变该变量的值
)
4)在现代C语言编译器中,修改const全局变量会导致程序崩溃
。因为现代C语言编译器,会将具有全局生命期的const变量放在只读存储区
,改了只读存储区的内容,造成崩溃。标准C语言编译器仍然将全局生命期的const变量放在全局数据区,所以是可以改变的。
5)const修饰函数参数,表示在函数体内不希望改变参数的值
(形参不可以出现在赋值符号左侧
)。
6)const修饰函数返回值,表示返回值不可改变
,多用于返回指针的情况(指针指向的值不可改变
)。
7)字符串字面量
存储于只读存储区
,必须使用const char*
来引用字符串字面量(这样在编译期会将修改只读存储区字面量的错误报出来);如果只是使用了char *
,此时编译的过
,但是运行时就会遇到段错误
。
8)volatile告诉编译器每次都必须去内存中去变量值
;volatile多用于多线程
环境中(变量可能在其他线程或其他地方被改变)
const和volatile
1、const
1.1 const修饰变量
const int const_global_i = 1; // const全局变量,全局生命期
int main()
{
const static int const_static_i = 2; // static局部变量,全局生命期
const int const_local_i = 3; // 普通局部变量
// const_local_i = 30; // error,在编译期不允许出现在赋值符号左侧
int* p = NULL; // 标准C语法需要将要使用的变量先声明。
p = (int*)&const_global_i;
*p = 10;
printf("const_global_i = %d\n", const_global_i);
p = (int*)&const_static_i;
*p = 20;
printf("const_statici = %d\n", const_static_i);
p = (int*)&const_local_i;
*p = 30;
printf("const_local_i = %d\n", const_local_i);
return 0;
}
- 在bcc编译器下(
标准C语言编译器
),这段代码可正常编译,测试的结果为三个变量的值均被改变,说明,在标准C编译器下,const定义的所有只读变量的值都可以改变
。 在gcc编译器下(扩展C编译器),这段代码可以编译的过,但是下面这两处赋值语句,在运行时都会发生
段错误
。说明,在现代C语言编译器下
,const定义
的只读变量如果是全局生命期(如全局变量、static变量)
,则会将该变量放到只读存储区
,修改了就会段错误。p = (int*)&const_global_i; *p = 10; printf("const_global_i = %d\n", const_global_i); p = (int*)&const_static_i; *p = 20; printf("const_statici = %d\n", const_static_i);
1.2 const修饰函数参数
const修饰函数参数表示在函数体内不希望改变参数的值
。
void func(const int a)
{
a = 2; // error, assignment of read-only parameter ‘a’
}
当使用const修饰了函数参数,在函数内部就不能改变形参的值。
特殊的:当函数的参数是指针时
,const
依然在保护某个值不可改变
,遵循“左数右指”
原则。
void func(const int* a)
{
*a = 1; // error,指向的数不可改变,即*a不能作为左值
}
void func(int* const a)
{
a = (int*)1; // error,指针不可改变,即a不可作为左值
}
1.3 const修饰函数返回值
const修饰函数返回值表示返回值不可改变
,多用于返回指针
的情形。
const int* GetInt()
{
int* p = (int*)malloc(4);
*p = 1;
return p;
}
int main()
{
const int* p = GetInt(); // 返回值为const的指针,也必须const的指针来接
// 在编译层面保证函数返回的指针指向的值不可改变
printf("*p = %d\n", *p);
*p = 2; // error,修改只读的值,read-only
return 0;
}
多用于函数返回值是指针
的情形,是因为,一般函数返回的这段内存,希望是只读的,在哪都不允许改变的。如果是一个普通的变量,函数返回的是一个副本
,也不存在修改原来的内存里的值
的情况。
1.4 字符串常量的类型?
C语言中的字符串字面量
存储于只读存储区
中,在程序中必须使用const char*
指针。
理解:字符串字面量存储于只读存储区中,自然在代码里,也需要使用const关键字来指明它的只读属性
。
// 错误示例
char* p = "Delphi"; // "Delphi"是一个字符串字面量,存储于只读存储区
p[0] = 'a'; // runtime error,段错误。修改只读存储区的自然会崩溃
// 正确用法:使用了const之后,错误的赋值在编译期就会报出来
const char* p = "Delphi"; // 使用const显示说明"Delphi"字面量的只读属性
p[0] = 'a'; // compile error,修改只读变量的值
2、volatile
- volatile可理解为“编译器警告指示字”,
禁止编译器优化
- volatile告诉编译器
每次必须去内存中取变量值
- volatile主要修饰可能被
多个线程
访问的变量 volatile也可以修饰可能被未知因素更改的变量
Demo示例int obj = 10; int a=0, b=0; a = obj; sleep(100); b = obj;
- 编译器在编译时发现obj
没有作为左值
使用,所以是不会改变
的,那么就直接去拿他的字面量使用
了,不用去访问内存了,多“聪明”(因为CPU访问内存是耗时的操作
,既然obj不变,所以拿字面量用也不会出错,而且更快了,两全其美)。 - 但实际上,如果这是一个
多线程程序
,或者是嵌入式中断处理程序
,在sleep的时候obj的值很可能在某个线程中或者某个中断处理程序中被改变了,这时候直接用字面量的值就不对了。
- 编译器在编译时发现obj
- 所以是否使用volatile关键字,要
视情况分析
,因为读内存是耗时的。
小问题:const voaltile int i = 0;变量i具有什么样的特性?编译器如何处理该变量?
答:const使得变量i具有只读属性,所以在程序中不可作为左值;volatile告诉编译器每次遇到要用变量i时,都必须去读i所代表的那段内存里的值。
本文总结自“狄泰软件学院”唐佐林老师《C语言进阶课程》。
如有错漏之处,恳请指正。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。