2

【C进阶】9、const和volatile分析

 3 years ago
source link: https://segmentfault.com/a/1190000040591198
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Summary

image.png

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的值很可能在某个线程中或者某个中断处理程序中被改变了,这时候直接用字面量的值就不对了。
  • 所以是否使用volatile关键字,要视情况分析,因为读内存是耗时的。

小问题:const voaltile int i = 0;变量i具有什么样的特性?编译器如何处理该变量?
答:const使得变量i具有只读属性,所以在程序中不可作为左值;volatile告诉编译器每次遇到要用变量i时,都必须去读i所代表的那段内存里的值。

本文总结自“狄泰软件学院”唐佐林老师《C语言进阶课程》。
如有错漏之处,恳请指正。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK