• c语言const、volatile问题小结


    之前百度面试的时候被volatile虐了,内核中很多地方也会用到,这个面试的时候出现概率太大了,所以搜集了一些结果供大家参考,大部分是百度到的,说得挺明确的,以后读代码的时候遇到了再更新。

    百度知道有人提如下问题:

    复制代码
    #include "stdio.h"
    int main(void){
        const char i = 1;
        char * j = (char *)&i;
        printf("%d,%d,%p,%p\n",i,*j,&i,j);//1,1
        *j = 2;
        printf("%d,%d,%p,%p\n",i,*j,&i,j);//1,2
        *j = 3;
        printf("%d,%d,%p,%p\n",i,*j,&i,j);//1,3
    }
    复制代码

    为什么i没有改变呢,各位运行也会发现,他们的地址都是一样的。

    醉了,我debug的时候,i也是随着j改变的,但是输出时,i就变成1了。希望各位能够从编译的角度上说一下这个问题。

    我对答案有稍微修改,对比vc编译结果差不多所以就引用别人的图了

    1.这是有const修饰与无const修饰的汇编代码

    变量i存储在eax寄存器中,有const修改表达寄存器的值不允许被修改

    第22行的时候,对*j=2;赋值时,有const修饰的会对edx进行操作

    而没有const进行修饰的就是直接对eax进行操作.

    至于编译器调试模式下,看见的i的值变成2,是因为编译器看见的是edx,实际输出的是1,参见第16行加了const之后,优化了printf函数的第一个%d对应的i,直接传的1,而没有const的时候,是传的i地址所取到的值进去,说到底还是编译优化问题,也是看内核看到这里搜到你的问题,加volatile确实可以强制编译器传i的地址去取值,在调试器里面看到的是寄存器的值所以是变化的

    2.站在逻辑的角度上,const修饰符就是限定变量的值不能被更改.至于后台怎么实现,你可以看看汇编,一条简单的赋值表达式也是由三条汇编语言实现的.

    3.const就是限定不能被更改,限定的是变量,变量不能被修改,限定的是指针,指针值不能被修改

      类中限定的是方法,方法不能更改成员变量的值,限定的是返回,返回值不能被修改

    volatile被设计用来修饰被不同线程访问和修改的变量。如果不加入volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。

    一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

    1). 并行设备的硬件寄存器(如:状态寄存器)

    2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

    3). 多线程应用中被几个任务共享的变量

    这是区分C程序员和嵌入式系统程序员的最基本的问题:嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所有这些都要求使用volatile变量。不懂得volatile内容将会带来灾难。

    假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是真正懂得volatile完全的重要性。

    1). 一个参数既可以是const还可以是volatile吗?解释为什么。

    2). 一个指针可以是volatile 吗?解释为什么。

    3). 下面的函数被用来计算某个整数的平方,它能实现预期设计目标吗?如果不能,试回答存在什么问题:

     

    1

    2

    3

    4

    int square(volatile int *ptr)

    {

        return ((*ptr) * (*ptr));

    }

    下面是答案:

    1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

    2). 是的。尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。

    3). 这段代码是个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

     

    1

    2

    3

    4

    5

    6

    7

    int square(volatile int* &ptr)//这里参数应该申明为引用,不然函数体里只会使用副本,外部没法更改

    {

        int a,b;

        a = *ptr;

        b = *ptr;

        return a*b;

    }

    由于*ptr的值可能在两次取值语句之间发生改变,因此a和b可能是不同的。结果,这段代码可能返回的不是你所期望的平方值!正确的代码如下:

     

    1

    2

    3

    4

    5

    6

    long square(volatile int*ptr)

    {

        int a;

        a = *ptr;

        return a*a;

    }

  • 相关阅读:
    WMS学习笔记:1.尝试加载WMS
    ftp文件上传和下载
    [WorldWind学习]19.WebDownload
    ASP.NET C# 如何在程序中控制IIS服务或应用程序池重启?
    asp.net mvc 全局权限过滤器及继成权限方法
    使用ActionFilterAttribute 记录 WebApi Action 请求和返回结果记录
    单点登录系统功能调试界面
    asp.net 读取RedisSessionStateProvider配置
    Redis-benchmark测试Redis性能
    ASP.NET MVC中移除冗余Response Header
  • 原文地址:https://www.cnblogs.com/hmxb/p/4901447.html
Copyright © 2020-2023  润新知