C的存储类别、存储说明符、限定符

1 对象:从硬件角度看,每个数据值都需要占用物理内存,这个物理内存在C语言中称之为对象;

  对象:可以存储一个或多个值的物理内存;

  对象使用"存储期"描述物理内存属性,"作用域"和"链接"来描述使用属性;

int i = 3;
/*该声明创建了标识符 i ,也就是存储着数据3的对象 i ;*/
/*程序通过创建标识符来表示特定的对象,实现访问物理内存;*/

  1.1 存储期:在内存中保存的时间;

    1.1.1 静态存储期:程序开始执行到结束的时期;静态指的是变量的内存地址不会改变,是静态的;供静态变量使用;

    1.1.2 自动存储期:程序进入变量所在块到退出变量所在块的时期;程序进入块时为块区域变量分配内存,退出块时释放内存;以堆栈的方式来处理;供自动变量使用;

    1.1.3 动态分配存储期:由程序调用malloc()等来分配,内存块较为分散导致速度慢;供动态内存函数分配使用;

    1.1.4 线程存储期:程序执行被分为多个线程,在某个线程中被声明之后一直存在直到线程结束的时期;属于以 _Thread_local声明的对象,每个线程都有该变量的私有备份;

  1.2 链接属性

    1.2.1 无链接属性:变量为当前块私有;

#include<stdio.h>
int count;
static int num; 
void main(void)
{
    int apple;/*作用域在当前块,无链接属性*/
    ....;
}

    1.2.2 内部链接属性:变量为当前文件私有;

#include<stdio.h>
int count;                 /*变量为外部链接属性,可与其他.h文件共享*/
static int num;            /*变量为内部链接属性,不与其他.h文件共享*/

int sum(int a, int b)
{
    static int sum_cnt=0;   /*sum_cnt具有静态存储期,所以sum_cnt从程序开始到结束一直存在,就算程序没调用sum函数,sum_cnt也存在;*/
    sum_cnt++;              /*由于sum_cnt定义在函数块内,所以sum_cnt只对当前函数可见;*/
    return a+b ;
}

void main(void)
{
    extern int count;      /*与前面的count是同一个count,此处声明可省略*/
    int count;             /*与前面的count不是同一个count,这个作用域为当前区域块*/
    count=sum(5,6);
}

    1.2.3 外部链接属性;

#include<stdio.h>
extern int count ;    /*在其他文件中已经声明的具有文件作用域的变量,本文件调用的话要用extern重新声明一下*/
int sum;              /*全局变量未初始化,会默认初始化为0,局部变量不会默认初始化*/
void main(void)
{ 
... 
}
/*关键字extern声明属于引用声明,extern声明不会引起内存分配,该变量必须在其他文件中有过定义声明(第1次声明)*/

  1.3 作用域:对象可以被访问的区域;

    1.3.1 文件作用域:从定义处到当前文件结尾的区域;定义在函数体外的变量具有文件作用域;有链接属性;

#include<stdio.h>
int units =0 ;                 /*具有文件作用域,被称为全局变量*/
int index ;                    //外部链接属性,静态存储期
static int inter_uints = 0 ;   //内部链接属性,静态存储期
int main(void)
{
   int x = 33;       //外层块变量
     while(x++<36)
   {
         int x =100 ; //内层块变量,与外层x不是一个变量;
         printf("%d,x here is 100",x);
     }
    printf("%d,x here is 33",x);
     for(i=0;i<3;i++)
     {
        static int stay = 1; 
        printf("%d,print stay result is 1,2,3",stay);
        stay++;
        //所有静态变量在程序载入内存时初始化已经执行完毕;此处写初始化表示作用域只在当前块区域;
      //静态变量只会初始化1次,具有时序记忆性;如果不赋值,默认初始化为0;
        //不能在函数的形参中使用static;
     }
}

    1.3.2 块作用域 :花括号括起来的区域;定义在块中的变量具有块作用域;无链接属性;

void function(int i,int j)
{                  /*i,j,k的块作用域*/
   int k;          //无链接属性,自动存储期
   static int p ;  //无链接属性,静态存储期
   for( int m=0;m<i;m++)
   {               /*m,n的块作用域*/  
     int n;    
   }   
}
/*其他函数可以通过指针形参或返回值,间接访问块区域的静态变量*/

    1.3.3 作用域不同的变量同名不冲突,作用域相同的变量同名冲突;

       构造类型在C中,有不同于普通变量的名称空间,名字相同也不会与普通变量冲突报错;

  1.4 存储类说明符

    1.4.1 register:寄存器类型

register int i ;
//寄存器变量存储在寄存器中,无法获取寄存器变量的地址
//如果寄存器不够,编译器就会将其设置为自动变量;

     1.4.2 auto:自动存储类型

auto int apple;
/*代码块内声明的变量默认为auto类型;无链接属性;自动变量不会默认初始值;*/

    1.4.3 static :静态存储类型

static int apple ;
/*不管声明在什么位置,作用域都是当前文件;并且从程序执行开始一直存在直到程序结束;全局变量(静态变量)默认初始值为0;*/

    1.4.4 extern:外部链接类型

extern int count ;    
/*在其他文件中已经声明的具有文件作用域的变量,本文件调用的话要用extern重新声明一下*/ /*关键字extern声明属于引用声明,extern声明不会引起内存分配,该变量必须在其他文件中有过定义声明(第1次声明)*/

 2 动态内存分配

  动态内存分配的优点是内存时在程序执行的时候根据需求确定的,不会浪费内存;

  像数组结构体之类的在内存空间需要在程序执行之前由编译器确定,可能会造成内存浪费或分配不足;

  2.1 malloc()与free()

double * ptr ;
ptr = ( double * ) malloc( 33*sizeof(double) ) ;
/*malloc()根据所需内存字节数33*sizeof(double) ,从物理内存中找到适合的空闲块,将该空闲块的首地址作为void类型的指针返回*/
/* void类型的指针可以被强制转换为任意类型的指针,如指向数组的指针,指向结构的指针等; */
/*malloc()动态分配不到内存,返回NULL空指针;*/

int * array;
int i=0;
array = (int *) malloc(10* sizeof(int) );
while(i<10 && scanf("%d", &array[i])==1)
i++;
printf("enter done ! ");
/*分配了一个10个int型的动态内存,然后将键盘输入填入内存*/ 
free(array);
/*释放malloc()动态分配的内存*/
int * array;
array = (int *) malloc(10* sizeof(int) );
free(array);
/*malloc()与free()在一个函数中应该成对使用;*/
/*如果一个函数每次调用malloc(),执行完之后没有调用free(),那么该函数返回后,指向malloc内存的指针释放,但是malloc分配的内存却还是在;*/
/*如此多次调用之后内存可能就会耗尽,这类问题称为内存泄漏;*/

  2.1 calloc()与free()

double * price;
price=(double *) calloc(100,sizeof(double));
/*calloc()分配了100个存储单元,每个单元的大小为一个double型  的动态内存;*/
/*并且calloc()会默认把块中的所有位都置为0;*/
...
free(price);
/*释放内存;*/

 3 ANSI C类型限定符

  3.1 const

const int size = 12 ;
const int days[12]={31,28,31,30,3,30,31,31,30,31,30,31};
/*const将变量声明为常量,只有第一次对变量赋值有效,之后赋值无效*/

const int size ;
size = 12 ;
/*此处为无效错误示范*/

    3.1.1 const 与指针

// const  double * ptr ;    常用于函数形参中
void sum(const double *ptr , int size);
{
    double rates[size];
    for(int i=0; i<size;i++)
    {    
        rates[i]=*ptr ;
        ptr++;
    }
    ...
}
/* const  double * ptr ;  表示该指针指向的数据类型不会被指针修改,但是指针指向的地址可以修改*/
/*需要注意:非const类型的数据地址可以赋值给const的地址,这只是表示我们使用该const变量的时候不会改变数据*/
/*另外一提:普通指针不能指向const数据类型:普通指针可以修改数据,但是const数据又不许修改*/

//double * const ptr2 ; 
double rates[5]={8.9,[3]=3.2};
double * const ptr2=rates;
*ptr2=100.5;
/* double * const ptr2 ; 表示该指针指向的地址不允许修改,但是该地址内的数据可以修改*/

/* const double * const ptr;  表示不修改指针指向的数据类型和存储的地址*/
/*  const double *ptr;和 double const * ptr;是等价的;*/

  3.2 volatile:告诉编译器不要优化编译,主要用于硬件地址以及在其他程序或其他线程中共享数据;

volatile int i ;
int val1,val2;
val1=i;
.../*一些不改变 i 值的代码*/
val2=i;
/*编译器第一次使用了i的值之后在寄存器中有临时缓存,val2再使用i值时默认从寄存器中提取,优化编译器速度;*/
/*使用volatile,则告诉编译器不要优化,应该从内存地址中查找使用值;该值可能会被其他程序或硬件改变了*/
volitile const int loc ;
volitile const int *ploc ;
/*表示 用const将硬件时钟设置为不能更改的变量,但是可以通过代理(其他程序或硬件)改变;我不理解这个意思*/
/*const 和 volitile 同时使用的顺序不重要,反正都是限定符;*/

  3.3 restrict :告诉编译器要优化编译,只能用于指针,表明该指针是访问对象唯一且初始的方式;

int * ptr = (int * ) calloc(100,sizeof(int));
ptr[2]+3;
prt[2]+5;
/*表示  指针ptr  是唯一且初始的访问动态内存的方式;*/
/*ptr[2]+3,ptr[2]+5 可以一起优化成 ptr[2]+8 编译;而不用担心中途ptr[2]的数值是否被其他代理改变了*/
int array[10];
int * parray = array ;
/*此处的指针parray并不是访问数组array的唯一方式,也不是初始方式;其他地方如果有使用,编译器不会去优化编译;*/

  3.4 _Atomic:声明原子变量,由stdatomic.h和threads.h管理;

#include<stdatomic.h>
#include<threads.h>
int
age; age = 12 ; /*可以替换如下,将其变为原子对象,当程序对原子对象进行操作时,其他线程不能访问该对象;*/ _Atomic int age ; atomic_store(&age,12) ; /*C11新特性,有的编译器不一定支持;*/

  

原文地址:https://www.cnblogs.com/caesura-k/p/12340843.html