zoukankan      html  css  js  c++  java
  • extern、static、restrict、volatile 关键字

     

    extern

    extern的两个作用:

    • 修饰变量或函数,提示编译器此变量或函数是在其它文件中定义的,但要在此处引用;
    • 进行链接指定,如: extern "C" void fun(int a, int b);  告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名而不是C++的,;

    static

    static的主要作用:

    • static局部变量,静态变量,函数返回时不会销毁;
    • static全局变量,仅在当前文件可见;
    • static函数,仅在当前文件可见;
    • 修饰类成员,静态成员为所有对象所有,不属于某个特定的对象;static成员函数没有this指针,只能访问类的static成员变量;

    另外,static变量如果没有初始化,会自动用0填充;

    例子: 用static成员实现singleton模式,

    class Singleton{ 
    public: 
        static Singleton* getInstance() { 
            if(!sg) { 
                sg = new Singleton();   //创建实例,但在哪里释放呢? 
            } 
            return sg; 
        } 
     
    private: 
        Singleton() { }            //构造函数声明为私有函数 
        static Singleton *sg;   //私有静态变量 
    };  
    
    //初始化静态变量sg为0,不指向任何对象 
    Singleton* Singleton::sg = NULL; 
     
    int main(){ 
        Singleton *sg = Singleton::getInstance(); 
     
        return 0; 
    }  

    restrict

    restrict 仅能修饰指针,用于告诉编译器只能通过该指针修改其指向的对象(内存区域),也就是说restrict指针将独占所指的内存,所有修改都得通过这个指针来,编译器可以放心大胆地把这片内存中前若干字节用寄存器cache起来。

    例如下面两个C库函数,都是从s2指向的位置复制n字节数据到s1指向的位置,且均返回s1的值。两者之间的差别由关键字restrict造成,即memcpy假定两个内存区域没有重叠;memmove函数则不做这个假定。

    void * memcpy(void * restrict s1, const void * restrict s2, size_t n);
    void * memove(void * s1, const void * s2, size_t n);

    volatile

    volatile 变量是随时可能发生变化的,编译器不要对其进行优化,以免出错。

    例如:

    int i=10; 
    int j = i; 
    ... 
    int k = i; 

    由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中,而不是重新从i里面读。

    又例如在嵌入式编程中,需要往某地址发送两条指令:

    int *ip =...;     //设备地址 
    *ip = 1;     //第一个指令 
    *ip = 2;     //第二个指令 

    以上代码可能会被编译器优化成:

    int *ip = ...; 
    *ip = 2;

    导致第一条指令丢失。

    如果用volatile关键字声明变量,就不允许编译器做优化:

    volatile int *ip =...;     //设备地址 
    *ip = 1;     //第一个指令 
    *ip = 2;     //第二个指令 

    定义为 volatile 的变量是说该变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。准确地说就是,编译器在用到volatile变量时必须从内存读取,而不能使用保存在寄存器里的值。

    下面是volatile变量的几个例子:

    1. 并行设备的硬件寄存器(如:状态寄存器);
    2. 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables);
    3. 多线程应用中的共享变量;

    volatile在多线程环境中的应用,可以先看下面的例子:

    volatile long int n = 0;
    
    void foo()
    {
        int i;
        for(i=0; i<10000; i++) {
            n++;
        }   
    }
    
    #define N 100
    
    int main()
    {
        int k;
        pthread_t th[N];
    
        for (k=0; k<N; k++) {
            pthread_create(&th[k], NULL, (void*)foo, NULL);
        }   
    
        for (k=0; k<N; k++) {
            pthread_join(th[k], NULL);
        }   
    
        printf("n=%ld
    ", n);
        return 0;
    }

    并发100个线程,每个线程对共享变量n累加1万次,理论上最后的结果应该是100w,但代码实际结果却是比这个更小的一个随机值。

    我们知道volatile修饰的变量在每次被线程访问时,都强迫从内存中重读该变量的值。而且,当变量值发生变化时,强迫线程将变化值回写到内存。这样在任何时刻,两个不同的线程总是看到某个变量的同一个值。

    既然如此,为何每次结果还是不同呢,这是因为n++操作并非原子性,也就是说volatile不能保证所修饰的变量进行原子操作。

    以汇编过程来看自增操作:

    mov        eax,dword ptr [ebp-8]     ;把i的值mov到eax寄存器
    add        eax,1                     ;自加
    mov        dword ptr [ebp-8],eax     ;再存放至i变量中 

    分为3个步骤:

    1)读取volatile变量值到寄存器;

    2)增加寄存器的值;

    3)把寄存器的值回写内存;

    可见,如果线程A在步骤2、3之间被另一个线程B抢占,线程B对共享变量完成一次自增,等到线程A继续完成步骤3时,就丢失了线程B的这次操作。

    要解决上面的多线程如何正确使用共享变量的问题,通常需要加锁,或者使用原子变量。

    先来看X86架构下面原子变量的定义:

    typedef struct {  
        volatile int counter;  
    } atomic_t;  

    原子类型其实是 int 类型,只是使用volatile禁止寄存器对其暂存。

    原子操作的API:

    atomic_read(atomic_t * v);                   // v
    atomic_set(atomic_t * v, int i);             // v = i
    void atomic_add(int i, atomic_t *v);         // v += i
    void atomic_sub(int i, atomic_t *v);         // v -= i
    void atomic_inc(atomic_t *v);                // v++
    void atomic_dec(atomic_t *v);                // v--
    int atomic_dec_and_test(atomic_t *v);        // (--v)==0?true:false
    int atomic_inc_and_test(atomic_t *v);         // (++v)==0?true:false
    int atomic_sub_and_test(int i, atomic_t *v);  // (v-i)==0?true:false
    int atomic_add_negative(int i, atomic_t *v);  // (v+i)<0?true:false
    int atomic_add_return(int i, atomic_t *v);   
    int atomic_sub_return(int i, atomic_t *v);
    int atomic_inc_return(atomic_t * v);
    int atomic_dec_return(atomic_t * v);
  • 相关阅读:
    Java入门第37课——猜字母游戏之设计数据结构
    Sublime Text 3 常用快捷键
    WEB前端响应式布局之BootStarp使用
    js让页面逐渐变透明,直到消失
    Vue实战之插件 sweetalert 的使用
    搭建jQuery开发环境
    Layui数据表单的编辑
    SpringBoot基于websocket的网页聊天
    layui修改数据的时候下拉框和选择框默认选中
    Linux 软件编译、安装、删除
  • 原文地址:https://www.cnblogs.com/chenny7/p/5799310.html
Copyright © 2011-2022 走看看