C语言_volatile、static、register、extern、auto、restrict和const

七种类型修饰符:
auto,const,register,static,volatile,extern,restrict

1. const
首先需要注意的是,const修饰的是在它前面的类型,如果它前面没有类型,那它修饰的是紧跟着它的那个类型。例如:
const int i = 0 ;和 int const i = 0 ; 完全相同  

    (a) const int *pi = 0;
    /* 相当于int const *pi = 0; pi是一个指向const int的指针,复引用此运算符为得到一个const int的类型,
该类型不能作为左值,在该语句后使用类似于*pi = 1的操作将导致编译错误。但该变量本身并不具备const属性,
可以使用pi = &i的操作。可用于访问只读存储器。
        */
    
    (b) int* const pi = 0;
    /* pi是一个指向int类型的const指针,复引用此运算符为得到一个int类型,该类型可以作为左值,在该语句可以使用类
似于*pi = 1的操作,但该变量本身具备const属性,使用pi = &i的操作将导致编译错误。可用于访问固定位置的存储器。
    */

    (c) const int* const pi = 0;
 /* pi和*pi均不能作为左值。它只适合于读取某个固定位置的只读存储器 */


const还有下列典型用法:
   * 用于参数列表,通常修饰的是指针类型,表明该函数不会试图对传入的地址进行写操作。例如:
void *memcpy(void *, const void *, size_t);

    * 用于返回值,通常是一个指向只读区域的指针。例如:
const datatype_t *get_fixed_item(int index);

    * 给固定不变的数据(例如码表)加上只读属性,在某些情况下可以减小ram的开销。

C中的指针有四种:
(1) type *p;
    /*指向变量数据的变量指针,指针的地址可以改变,其指针指向的内容也可以改变;*/

(2) type const *p;
    /*指向常量数据的变量指针,指针的地址可以改变,但其指向的内容不允许改变;*/

(3) type *const p;
    /*指向变量数据的常量指针,指针的地址不允许改变,但其指向的内容可以改变;*/

(4) type const * const p;
    /*指向常量数据的常量指针,指针的地址不允许改变,其指向的内容也不可以改变。*/

**********************************************************************************

2.static
        static用于全局变量声明和局部变量声明具有完全不同的语义,不得不说,这是C语言设计中的一个不合理之处。
        当static用于修饰全局变量声明 (或函数声明,可以认为函数声明就是声明一个指向代码段的指针,该指针的值最后由链接时决定,从这个意义上说,函数声明也是一种全局变量声明),它表示该变量具有文件作用域,只能被该源文件的代码引用,不能被其他源文件中的代码访问。在编译时引起的实际变化是被static修饰的变量不会被写入目标文件的输出节,在链接时解析其他模块中的未定义符号时不会被引用到。它的反义词是extern。(其作用是:在进行大项目时,避免每个程序员的命名变量名时的冲突,因为static修饰的全局变量的作用域在该文件内)
        当static用于修饰局部变量声明,它表示该变量不是分配在该函数的活动记录中,而是分配在全局的数据段(或bss段)中。简单的说,就是被 static修饰的局部变量实际上并不是局部变量,而是具有函数作用域的全局变量,除了只能在定义它的函数内访问外(这是由C语法决定的),它的运行时特 征和全局变量完全一样,函数返回不会影响它的状态,它的初始化仅有一次,发生在程序的装载时,而不是在每次函数调用的时候初始化。它的反义词是auto。(静态局部变量的作用域不变,生命周期延长至整个源程序)

C++类支持静态成员变量,在使用的时候,遵循类内声明,类外定义的原则。即在类内部进行声明该变量的类型以及静态属性,但不可以赋初始值,而是要在类外定义,同时赋初始值。

class class_type
{
   static var_type var; 
};
var_type class_type::var = init_value;
初始值部分,=init_value可以省略,省略时默认赋值为0。


在使用上:
1 成员函数均可使用静态变量;
2 如果静态变量属性为public,那么任意一个对象a均可使用静态变量var,使用形式为 a.var;
3 如果静态变量属性为public,那么可以用如下形式,在类class_type有效的范围内,使用静态变量var:


******************************************************************************************************
3.volatile
volatile修饰符的作用是告诉优化器不能优化这个变量的读写操作,一定要为这个变量的读写

操作生成代码。

 volatile的语法和const的语法是一样的,但是volatile的意思是“在编译器认识的范围外,这个数据可以被改变”。环境在不知原因的条件下改变数据,所以,volatile在告诉编译器不要擅自做出有关数据的任何假定,在优化期间这是特别重要的。

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

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

    a、并行设备的硬件寄存器,如状态寄存器,处理器的状态不停的在变化,所以寄存器的值也在不断的随处理器状态的变化而变化。

    b、一个中断服务子程序中会访问到得非自动变量

    c、多线程应用中被几个任务共享的变量。每个线程都有可能会修改变量的值。

    

    下面一个关于volatile的问题:

    一个参数既可以是const也是volatile吗?一个指针可以使volatile吗?

    第一个问题:是的,最好的例子就是只读的状态寄存器。他是volatile,因为它可能会被意想不到的改变;他又是const,因为程序不应该试图去修改它。

    第二个问题:是的。尽管这并不常见,一个例子就是当中断服务子程序修改一个指向一个buffer的指针时。 


******************************************************************************************************
4.auto
描述:auto关键字在我们写的代码里几乎看不到,但是它又无处不在,它是如此的重要,又是如此的与世无争,默默的履行着自己的
义务,却又隐姓埋名。
作用:C程序是面向过程的,在C代码中会出现大量的函数模块,每个函数都有其生命周期(也称作用域),在函数生命周期中声明
的变量通常叫做局部变量,也叫自动变量。


******************************************************************************************************
5.register
作用:如果一个变量被register来修辞,就意味着,该变量会作为一个寄存器变量,让该变量的访问速度达到最快。比
如:一个程序逻辑中有一个很大的循环,循环中有几个变量要频繁进行操作,这些变量可以声明为register类型。
寄存器变量:寄存器变量是指一个变量直接引用寄存器,也就是对变量名的操作的结果是直接对寄存器进行访问。寄存器是CPU的
亲信,CPU操作的每个操作数和操作结果,都由寄存器来暂时保存,最后才写入到内存或从内存中读出。也就是说,变量的值通常
保存在内存中,CPU对变量进行读取先是将变量的值从内存中读取到寄存器中,然后进行运算,运算完将结果写回到内存中。为什
么要这么设计,而不直接对变量的值从内存中进行运算,而要再借助于寄存器?这是由于考虑到性能的问题才这么设计的。在计
算机系统中,包含有很多种不同类型的存储器,如表xxx所示。
******************************************************************************************************


6.extern

基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定。

           也就是说extern有两个作用,第一个,当它与"C"一起连用时,如: extern "C" void fun(int a, int b);则告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名而不是C++的,C++的规则在翻译这个函数名时会把fun这个名字变得面目全非,可能是fun@aBc_int_int#%$也可能是别的,这要看编译器的"脾气"了(不同的编译器采用的方法不一样),为什么这么做呢,因为C++支持函数的重载啊,在这里不去过多的论述这个问题,如果你有兴趣可以去网上搜索,相信你可以得到满意的解释!
第二,当extern不与"C"在一起修饰变量或函数时,如在头文件中: extern int g_Int; 它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块活其他模块中使用,记住它是一个声明不是定义!也就是说B模块(编译单元)要是引用模块(编译单元)A中定义的全局变量或函数时,它只要包含A模块的头文件即可,在编译阶段,模块B虽然找不到该函数或变量,但它不会报错,它会在连接时从模块A生成的目标代码中找到此函数。

 问题:extern 变量
  在一个源文件里定义了一个数组:char a[6];
  在另外一个文件里用下列语句进行了声明:extern char *a;
  请问,这样可以吗?
  答案与分析:
  1)、不可以,程序运行时会告诉你非法访问。原因在于,指向类型T的指针并不等价于类型T的数组。extern char *a声明的是一个指针变量而不是字符数组,因此与实际的定义不同,从而造成运行时非法访问。应该将声明改为extern char a[ ]
  2)、例子分析如下,如果a[] = "abcd",则外部变量a=0x61626364 (abcd的ASCII码值),*a显然没有意义
  显然a指向的空间(0x61626364)没有意义,易出现非法内存访问。
  3)、这提示我们,在使用extern时候要严格对应声明时的格式,在实际编程中,这样的错误屡见不鲜。
  4)、extern用在变量声明中常常有这样一个作用,你在*.c文件中声明了一个全局的变量,这个全局的变量如果要被引用,就放在*.h中并用extern来声明

extern详情:http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777431.html

7、restrict

概念:

  restrict,C语言中的一种类型限定符(Type Qualifiers),用于告诉编译器,对象已经被指针所引用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。

渊源:

  restrict是c99标准引入的,它只可以用于限定和约束指针,并表明指针是访问一个数据对象的唯一且初始的方式.即它告诉编译器,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改;这样做的好处是,能帮助编译器进行更好的优化代码,生成更有效率的汇编代码.如 int *restrict ptr, ptr 指向的内存单元只能被 ptr访问到,任何同样指向这个内存单元的其他指针都是未定义的,直白点就是无效指针。restrict 的出现是因为 C 语言本身固有的缺陷,C 程序员应当主动地规避这个缺陷,而编译器也会很配合地优化你的代码.

使用场景:

非常需要性能。
需要改写指针的所指物。
明确知道某两个指针在业务逻辑上不会、也不能重叠
理论解释较为抽象,根据《C Primier Plus 6th》的解释,举出以下例子:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <string.h>

int main()
{
    int i = 0;
    int arr[10] = {0};
    int *parr = arr;
    int * restrict rstar = (int *)malloc(sizeof(int)*10);
    memset(rstar, 0x00 ,sizeof(int)*10);
    
    for(i = 0; i < 10; i++)
    {
        #if 1
        rstar[i]  += 5;
        parr[i] += 5;
        arr[i] -= 2;
        parr[i] += 3;
        rstar[i]  += 3;
        #endif
    }

    if(rstar != NULL)
    {
        free(rstar);
        rstar = NULL;
    }
    return 0;
}

该例子编译时需要加上  -std=c99 选项

        其实上述例子看不出什么问题,只是编译器在会进行一定的优化。由于用 restrict 关键字修饰的指针 rstar ,在编译过程中,编译器认为,只有 rstar 指针可以访问上面 malloc 的区域。故在循环体中的rstar[i]  += 5;和rstar[i]  += 3;能合成一条

rstar[i]  += 8;以做优化。像是 arr 或是 parr ,能够访问到其地址区域的方式太多,编译器不敢贸然进行优化,故就按照正常流程一点一点编译。书中还举了一个memcpy的例子更为形象。

        在C99中, 这两个函数的原型是:
void * memcpy(void * restrict s1, const void * restrict s2, size_t n);
void * memmove(void * s1, const void * s2, size_t n);

        在memcpy中,指针s1 和 指针s2 都加上了 restrict 优化,其意义为,s1的指向的内存区域只能用s1来调用,s2的指向的内存区域只能用s2来调用,在restrict 的限定下,s1指向的区域与s2指向的区域相重合(重叠的意思也就是s1指向的区域有可能被s2指针应用,或是相反)是不允许的,故利用memcpy不会出现两个内存区域重合的情况。memmove则要考虑复制内存重合的情况。
 

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值