C语言细节 存储类别与内存管理

一.描述对象与标识符
0.对象与标识符:

被存储的每个值都占用一定的物理内存,C语言把这样1块内存称为1"对象"(Object).对象可以存储1/多个值,
也可能并未存储实际的值,但它在存储适当的值时一定具有相应的大小

程序需要1种访问对象的方法,这可以通过声明变量来实现:
int entity=3;
该声明创建了1个名为entity的"标识符"(Identifier)"指定"(Designate)特定对象并提供存储在对象中的值.
也可以通过其他方式来指定对象:
int * pt=&entity;
int ranks[10];
pt是1个标识符,指定了1个用于存储地址的对象;而表达式*pt不是标识符,因为它不是1个名称,但却指定了1个对象.
这些指定对象的表达式通常称为"左值"(Lvalue).根据定义,ranks+2*entity不是标识符(不是名称)也不是左值(
不指定内存上的内容),而表达式*(ranks+2*entity)是左值,因为其指定了特定内存位置处的值(这里是数组ranks
中的第7个值).ranks的声明创建了1个可容纳10int类型元素的对象,并且每个元素也是1个对象

如果可以通过左值改变其指向的值,该左值就是1"可修改的左值"(Modifiable Lvalue).使用const标识符只能
保证不能通过该变量修改其指向的对象的值,但变量可以指向其他对象,所以即使使用const,变量还是可修改的左值:
const char * pc="Be hold a string literal!";
由于const只能保证被pc指向的字符串的内容不能通过pc修改,但pc可以指向其他字符串,所以pc是可修改的左值;*pc指定了存储'B'的数据对象,所以是左值但不是可修改的左值(无法通过*pc指向其他对象)

可以用"存储期"(Storage Duration)描述对象,即对象在内存中保留多长时间(生存期);标识符用于访问对象,可
以用"作用域"(Scope)"链接"(Linkage)来描述,即程序的哪些部分可以使用它(可见性)

1.作用域(Scope):

C变量的作用域可以是:
1."块作用域"(Block Scope):
①可见范围是从定义处到包含该定义的块的结尾
②函数的形参/"局部变量"(Local Variable)都具有块作用域
double blocky(double cleo) {//cleo的作用域开始
    double patrick=0.0;//patrick的作用域开始
    int i;//i的作用域开始
    for (i=0;i<10;i++) {
    	double q=cleo*i;//q的作用域开始
    	patrick*=q;//q的作用域结束
    }
    return patrick;
}//cleo,patrick,i的作用域结束
③过去,具有块作用域的变量都必须声明在块的开头,C99标准放宽了该限制,允许在块中的任意位置声明变量,因此现在
可以这么写:
for (int i=0;i<10;i++) {//i的作用域开始
    printf("A C99 feature:i=%d\n",i);
}//i的作用域结束
为了适应该新特性,C99把块的概念从函数扩展到for/while/do while/if语句

2."函数作用域"(Function Scope):
①可见范围是包含定义的函数
②仅用于goto语句的标签,这意味着即使标签出现在函数的内层块中,其作用域也延伸至整个函数:
int g(int size,double cost) {//a,b的作用域开始
    if (size>12) {
        goto a;
    }
    goto b;
    a:cost*=1.05;
    int flag=2;
    b:bill=cost*flag;
}//a,b的作用域结束
③如果在2个块中使用相同的标签会很混乱,标签的函数作用域防止了这种情况的发生

3."函数原型作用域"(Function Prototype Scope):
①可见范围是从形参定义处到原型声明的结尾
②仅用于函数原型中的形参:
int mightly(int mouse/*mouse的作用域开始*/,double large/*large的作用域开始*/);//mouse,large的作用域结束
③这意味着编译器在处理函数原型中的形参时只关心其类型,而形参名通常无关紧要(对变长数组除外),可有可无,即使
有也可以和函数定义中的形参名不同

4."文件作用域"(File Scope):
①可见范围是从定义处到包含该定义的文件(翻译单元)的结尾
②用于"全局变量"(Global Variable;即定义在函数外的变量):
#include <stdio.h>
int units=0//units的作用域开始
void critic(void) {
    printf("critic:%d\n",units);
}
int main(void) {
    printf("main1:%d\n",units);
    critic();
    printf("main2:%d\n",units);
    return 0;
}//units的作用域结束

2.链接(Linkage):

C变量有3种链接属性:
1."外部链接":
具有文件作用域的变量如果没有使用static关键字,则是外部链接的("外部链接的文件作用域"),这意味着它们可以在
多文件程序中被使用

2."内部链接":
具有文件作用域的变量如果使用了static关键字,则是内部链接的("内部链接的文件作用域"),这意味着它们只能在1
个翻译单元中被使用

注:有时将"内部链接的文件作用域"称为"文件作用域","外部链接的文件作用域"称为"全局作用域"/"程序作用域"

3."无链接":
具有块/函数/函数原型作用域的变量都是无链接的,这意味着它们是定义它们的块/函数/函数原型私有的

3.存储期(Storage Duration):

存储期描述了通过标识符访问的对象的生存期,C对象有4种存储期:
1."静态存储期":
文件作用域变量指向的对象具有静态存储期,这意味着其指向的对象在程序的执行期间一直存在
  //注:无论是内部链接还是外部链接,文件作用域变量指向的对象都具有静态存储期
使用了static关键字的块作用域变量指向的对象也具有静态存储期(不过,只有在定义该变量的块中,才能通过定义时
使用的变量名访问该对象)

2."线程存储期":
用于并发程序设计,具有线程存储期的对象从被声明时到线程结束时一直存在
以关键字_Thread_local声明1个对象时,每个线程都获得该变量的私有备份

3."自动存储期":
块作用域变量指向的对象通常都具有自动存储期,程序进入定义变量的块时为该变量分配内存,退出该块时,释放为该变
量分配的内存,这种方法相当于把自动变量占用的内存视为1个可重复使用的工作区/暂存区,例如1个函数调用结束后,
其变量占用的内存可用于存储下1个函数的变量
不过,变长数组的存储期是从声明处到块的结尾处,这与其他具有块作用域的变量不同
另外,如果块作用域变量使用了static关键字,其指向的对象将具有静态存储期而非自动存储期

4."动态分配存储期":
参见 C语言基础.动态内存分配 与 C语言细节.动态内存分配 部分

二.存储类别(Storage Class)

存储类别存储期作用域链接声明方式存储类别说明符
自动自动块内auto
寄存器自动块内,使用关键字registerregister
静态外部链接静态文件外部所有函数外extern
静态内部链接静态文件内部所有函数外,使用关键字staticstatic
静态无链接静态块内,使用关键字staticstatic
//存储类别的选择:
1.最常用的存储类别是自动变量,因为这是默认的存储类别
2.不要随意使用外部变量,否则函数可以修改其他函数使用的变量,唯一的例外是const数据
3.保护性程序设计的黄金法则是"按需知道"原则,即尽量在函数内解决该函数的任务,只共享必须共享的变量

1.自动变量:

属于自动存储类别的变量具有自动存储期,块作用域且无链接.默认情况下,函数形参/局部变量都属于自动存储类别;
为了更清楚地表明意图,也可以显示使用auto说明符:
int main(void) {
    auto int plox;
}
不过,auto关键字在C++中的用法完全不同,如果要编写兼容C/C++的程序,不要使用auto作为存储类别说明

自动变量不会初始化,除非显式初始化该变量:
int main(void) {
    int repid;//未初始化
    int tents=5;//显示初始化
    int rance=5+tents;//使用"非常量表达式"(Non-Constant Expression)显示   初始化
}

2.寄存器变量:

寄存器变量存储在寄存器中,或者概括地说,最快可用内存中.与普通变量相比,访问/处理寄存器变量的速度更快,将频
繁使用的变量声明为寄存器变量可提供程序速度.由于寄存器变量存储在寄存器中,所以无法获取其地址.寄存器变量也
具有自动存储期,块作用域且无链接.使用register说明符即可声明寄存器变量:
int fast(register int rapid) {
    register int quick;
}

声明寄存器变量更像是1种请求而不是直接命令,编译器根据寄存器或最快可用内存的数量来衡量请求,或者直接忽略请
求,所以请求可能不能如愿,这时寄存器变量就变成普通的自动变量,但即使如此,仍不能获取其地址

受硬件寄存器长度的限制,可声明为寄存器变量的数据类型有限,只能是char/int/指针.另外,只有局部自动变量和形
参才能被声明为寄存器变量,全局变量和局部静态变量则不行.

由于当今的优化编译系统能够识别使用频繁的变量,从而自动将这些变量放在寄存器中,而无需手动指定,因此,显式使
用register声明变量实际上是不必要的

3.静态变量

"静态变量"(Static Variable)是指该变量在内存中的位置不变,而不是说其值不能改变.具有
文件作用域的变量自动具有(也只能具有)静态存储期

(1)块作用域的静态变量:

"块作用域的静态变量"又称"局部静态变量""内部静态存储类别"(Internal Static Storage Class),这里的
"内部"指的是"函数内部".在块中通过static说明符创建该类变量.这种变量具有块作用域且无链接,但具有静态存储
期,也就是说,在程序离开定义该变量的函数后,该变量会留在原位置而不会消失:
#include <stdio.h>

void trystat(void) {
	int fade=1;
	static int stay=1;
	printf("%d,%d\n",fade++,stay++);
}

int main(void) {
	int count;
	for (count=1;count<=3;count++) {
		printf("%d:",count);
		trystat();
	}
	return 0;
}
//结果:
1:1,1
2:1,2
3:1,3

//说明:
每次调用trystat()时fade都被初始化为1,而stay只在编译trystat()时被初始化.对fade的声明是trystat()
的一部分,因此每次调用trystat()都会执行该声明;而对stay的声明并不是trystat()的一部分,实际上,静态变量
/外部变量的声明在程序被载入内存时就已执行完毕.把该声明放在trystat()中只是为了告诉编译器只有trystat()
才能看到该变量

//注意:
1.如果未显示初始化int类型的静态变量,其会被初始化为0
2.不能对函数的形参使用static说明符:
int no(static int flu);//不合法

(2)外部链接的静态变量(Static Variable with External Linkage):

"外部链接的静态变量"具有文件作用域&外部链接&静态存储期,又称"外部变量"(External Variable)/"外部存储类别"
(External Storage Class)

//关于声明:
将变量定义在所有函数外便创建了外部变量:
int errupt=10;//外部变量
extern double up[100];//外部变量(被定义在其他文件中)
int main(void) {
    extern int errupt;//可选的声明;用于说明main()使用了errupt
    extern double up[];//可选的声明;这里可以不声明up的大小,因为第1次声明时已指明
    ...
}
不带extern说明符的声明是用于定义外部变量的,称为"定义式声明"(Defining Declaration);而对带有extern
说明符的声明,编译器会假设该变量定义在别处(该文件内或其他文件中),因此,这种声明用于引用已被定义的外部变
量,称为"引用式声明"(Referencing Declaration)
以上述代码为例,外部变量errupt被定义在该文件中,extern说明符是可选的.而up被定义在其他文件中,故必须加
extern说明符
为了告知编译器main()使用了外部变量errupt/up,可在函数中再次声明,这种情况下extern说明符不可省略,否则
会创建独立的局部变量.
在定义了局部变量的块中,同名的外部变量会被"屏蔽".为提高可读性,尽量不要让外部变量和局部变量的变量名相同.
如果不得已必须使用与外部变量同名的局部变量,可在局部变量的声明中使用auto说明符来显式表达这一意图

//关于初始化:
只能使用常量表达式来初始化外部变量(更一般地说,具有文件作用域的变量).如果没有显式初始化外部变量,其会被初
始化为0' ':
#include <stdio.h>
int i;
char c;
//int j=i;//报错:[Error] initializer element is not constant
void f(void) {
    printf("%d",i);//结果:12
}
int main(void) {
	printf("%d,%c",i,c);
	printf("aaa\n");//结果:0, aaa
	i=12;
	f();
	return 0;
}
外部变量只能初始化1,且只能在定义该外部变量时进行.如下代码是错误的:
extern char permies='y';//该声明是引用式声明,不能初始化permies
int main(void) {...}

//外部名称:
C99/C11标准均要求编译器识别局部标识符的前63个字符和外部标识符的前31个字符,而更早的标准要求编译器识别
局部标识符的前31个字符和外部标识符的前6个字符.外部变量名的命名规则比局部变量名更严格,因为外部变量名还
需要遵循局部环境规则,所受的限制更多

(3)内部链接的静态变量(Static Variable with Internal Linkage):

"内部链接的静态变量"具有静态存储期&文件作用域&内部链接,又称"外部静态变量"(External Static Variable),
虽然该别名有些自相矛盾(毕竟这类变量具有内部链接而不是外部链接),但却没有其他合适的简称.在所有函数外通过
static说明符创建该类变量:
static int svil=1;
int main(void) {
    //extern int svil;//引用式声明
    print("%d",svil);
    return 0;
}
为了告知编译器main()使用了svil,可在main()中用extern说明符再次声明svil,这种声明不改变变量的链接属性

(4)多文件:

只有当程序由多个翻译单元组成时,内部链接和外部链接的区别才体现出来. 复杂的C程序通常由多个源代码文件组成.
这些文件可能需要共享1个外部变量,C通过在1个文件中进行外部变量的定义式声明,并在其他文件中进行引用式声明来
实现该需求.其中的引用式声明均需要使用extern说明符,而定义式声明不需要.并且,只有在定义式声明中才能初始化
变量.要注意的是,定义式声明只是单方面允许其他文件使用该变量,其他文件中的引用式声明不能省略.另外,旧的编译
器可能遵循与此不同的规则

4.存储类别说明符

C语言中的"存储类别说明符"(Storage-Class Specifier)共有6:auto,register,static,extern,typedef,
_Thread_local.其中typedef与任何存储类别都无关,把它归于存储类别说明符是出于语法上的原因.多数情况下都不能
在声明中使用多个存储类别说明符,只有_Thread_local例外,它可以和static/extern一起使用

在这里插入图片描述
在这里插入图片描述
5.函数的存储类别:

函数也有存储类别,可以是"外部函数"(默认)/"静态函数"/"内联函数"(C99中新增).外部函数可被其他文件中的函数访问,而静态函
数只能用于其定义式声明所在的文件.可通过如下声明确定函数的存储类别:
double gamma(double);//默认为外部函数
static double beta(int,int);//静态函数
extern double delta(double,int);//外部函数
对函数存储类别的声明以引用式声明中的为准;且不允许省略引用式声明中的存储类别说明再在定义式说明中指定存储类别为static,
因为编译器读到引用式声明时会假定函数的存储类别为extern.通常用extern关键字来声明定义在其他文件中的函数,以表明该函数
被定义在别处

三.动态内存分配
1.语法:

参见 C语言基础.动态内存分配 部分

2.动态数组:

变长数组和通过动态分配内存得到的数组称为"动态数组"(Dynamic Array),2种方法都能创建在
运行时确定大小的数组:
int DynmcArry() {
    int n;
    int * pi;
    scanf("%d",&n);
    pi=(int *)malloc(n*sizeof(int));//动态内存分配
    int ar[n];//变长数组(VLA)
    pi[2]=ar[2]=-5;
}
不过在C99之前不允许变长数组,但允许动态内存分配,因此动态内存分配比变长数组更灵活
不同之处在于,变长数组是自动存储类型,因此不需要手动释放内存;而动态分配的内存需要手动释放.
对多维数组而言,使用变长数组更方便;当然,使用malloc()也能达到目的,不过语法较繁琐

3.存储类别与动态内存分配:

考虑1个理想化的模型:程序把分配给它的内存分为3部分,一部分供静态变量使用,另一部分供自动变量使用,最后
一部分用于动态内存分配(这部分内存称为"内存堆""自由内存").静态变量使用的内存数量在编译时确定;只要
程序还在运行,就能访问这部分内存;该类变量在程序开始时被创建,在程序结束时被销毁.自动变量在进入该变量
所在的块时存在,离开该块时消失;因此,自动变量占用的内存数量会不断变化;这部分内存通常作为栈来处理.动态
分配的内存在从分配到释放的这段时间内存在,由程序员管理,因此这部分内存通常会支离破碎,即未使用的内存块
分散在已使用的内存块之间;另外,使用动态内存通常比使用栈内存慢.总而言之,程序把静态对象/自动对象/动态
分配的对象存储在不同区域:
#include <stdio.h>
#include <stdlib.h>
#include <string.h> 

int static_store=30;
const char * pcg="String Literal";

int main(void) {
	int auto_store=40;
	char auto_string[]="Auto char array";
	int * pi;
	char * pcl;
	pi=(int *)malloc(sizeof(int));
	*pi=35;
	pcl=(char *)malloc(strlen("Dynamic String")+1);
	strcpy(pcl,"Dynamic String");
	printf("static_store:%d at %p\n",static_store,&static_store);
	printf("  auto_store:%d at %p\n",auto_store,&auto_store);
	printf("         *pi:%d at %p\n",*pi,pi);
	printf(" %s at %p\n",pcg,pcg);
	printf("%s at %p\n",auto_string,auto_string);
	printf(" %s at %p\n",pcl,pcl);
	printf("  %s at %p\n","Quoted String","Quoted String");
	free(pi);
	free(pcl);
	return 0;
}
//结果:
static_store:30 at 0000000000403010//40xx区(静态数据,包括字符串字面量)
  auto_store:40 at  000000000062FE0C//62xx区(自动数据)
         *pi:35 at 00000000001713C0//17xx区(动态分配的数据)
 String Literal at 0000000000404000//40xx区
Auto char array at 000000000062FDF0//62xx区
 Dynamic String at 00000000001713E0//17xx区
  Quoted String at 0000000000404069//40xx区

四.ANSI C类型限定符
1.简介,语法,属性:

通常用"类型"(int)"存储类别"(static)来描述1个变量.而C90又新增了2个属性:"恒常性"(Constancy)"易变性"(Volatility),分别用关键字constvolatile来声明,2个关键字称为"(类型)限定符".C99新增
了第3个限定符:restrict,用于提高编译器优化.C11新增了第4个限定符:_Atomic,并提供了stdatomic.h库,以
支持并发程序设计,不过这些是可选的支持项.以这4个关键字创建的类型是"限定类型"(Qualified Type)

语法:
[<qualifier1> <qualifier2> ... ]<type> <var>[=<val>];
  //参数说明:
    qualifier:限定符
    type:数据类型
    var:变量名
    val:变量的值

C99还为类型限定符增加了1个新属性:类型限定符是"幂等的"(Idempotent),即在1条声明中多次使用同1个限定符
和只使用1个的效果相同:
const const const int n=6;//相当于const int n=6;
有了该属性,就可以编写类似下面的代码:
typedef const int zip;
const zip q=8;
  • 关键字的新语法:
C99允许把类型限定符放在函数原型和函数头中的形参的初始方括号中,:
void ofmouth(int a1[const],int a2[restrict],int n);
这种语法功能和类型限定符原本的语法相同,且同时适用于数组表示法和指针表示法

C99还允许将static存储类型说明符放在形参的初始方括号中,不过此时static除了说明存储类
型外,还告知编译器如何使用形参,:
double stick(double ar[static 20]);
这里static说明stick()中调用的实参应是1个指向数组首元素的指针,且该数组至少有20个元
素.这种用法的目的是让编译器使用这些信息优化函数的编码.C委员会不愿意创建新的关键字,因
为这会让以前使用新关键字作为标识符的程序失效,因此利用了已有的关键字static

2.const类型限定符:

const关键字声明的对象,其值不能通过赋值/递增/递减来修改

//#####################################################################

//用于普通变量:
#include <stdio.h>

int main(void) {
	const int nochange=12;
	//不初始化后就不能再修改nochange(即使初始化时没有指定其值)
	nochange=6;//不允许
	return 0;
}
//结果:
[Error] assignment of read-only variable 'nochange'

//用于数组:
#include <stdio.h>

int main(void) {
	const int nochangearray[]={1,2,3};
	nochangearray[1]=10;
	return 0;
}
//结果:
[Error] assignment of read-only location 'nochangearray[1]'

//#####################################################################

//用于指针指向的值:
#include <stdio.h>

int main(void) {
	const float * cpf;//cpf指向1个float类型的const值
	//相当于float const * cpf;
	float f=12.33;
	cpf=&f;
	//*cpf=13.44; 
	f=22.33;//虽然不能通过cpf修改f的值,但可以通过其他途径修改f的值
	printf("%f\n",f);
	float f2=100.01;
	cpf=&f2;//允许修改cpf本身的值(如使其指向另1个地址)
	printf("%f",*cpf);
	return 0;
}
//结果:
//[Error] assignment of read-only location '*cpf'
22.330000
100.010002

//用于指针本身:
#include <stdio.h>

int main(void) {
	float f=12.33,f2=111.11;
	float * const cpf=&f;//不能改变cpf的值(即cpf指向的地址)
	//注意:这里其实是把cpf设为了只读变量,因此即使没有对cpf进行初始化,之后也不能再对其赋值
	//cpf=&f2;
	*cpf=123.32;
	printf("%f",f);
	return 0;
}
//结果:
//[Error] assignment of read-only variable 'cpf'
123.320000

//#####################################################################

//用于形参:
#include <stdio.h>

void display(const int array[],int limit);

int main(void) {
	int array[]={1,2,3};
	display(array,1);
	return 0;
}

void display(const int array[],int limit) {
  //保证了array的值在display()内部不会被修改
  //另外,函数定义中的const也不可省略,否则会和函数原型中的声明冲突
	array[limit]=100;
}
//结果:
[Error] assignment of read-only location '*(array + (sizetype)((long long unsigned int)limit * 4ull))'

//#####################################################################

//用于全局变量:
全局变量将数据暴露给了所有函数,这具有风险.但使用const限定符可以避免该风险
不过,在文件间共享const数据要小心,可以采用2种方案:
①遵循外部变量的常用规则,即在1个文件中使用定义式声明,在其他文件中使用引用式声明:
//file1.c中:
const double PI=3.1415926;
const char * MONTHS[12]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"};
//file2.c中:
extern const double PI;
extern const char * MONTHS[];
②把全局const变量放在1个头文件中,然后在其他文件中包含该头文件:
//constant.h中:
static const double PI=3.14159;//关于static关键字,见下图
static const * MONTHS[12]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"};
//file1.c中:
#include <constant.h>
//file2.c中:
#include <constant.h>
使用头文件可以减少引用式声明的数量,降低代码量;缺点是会导致重复的数据

在这里插入图片描述
3.volatile类型限定符:???

参见:https://www.cnblogs.com/wuyepeng/p/9784922.html
https://blog.csdn.net/lin_credible/article/details/17128057

volatile限定符告知计算机,代理(而不是变量所在的程序)可以改变该变量的值.通常用于硬件地址及在其他程序或同时运行的线程中共享数据.例如,1个地址上可能存储着当前的时钟时间,无
论程序做什么,地址上的值都随时间的变化而变化;1个地址用于接受另1台计算机传入的信息
语法和const限定符相同:
volatile int locl;//locl是1个易变的变量
volatile int * ploc;//ploc是1个指向易变的变量的指针

volatile限定符涉及编译器的优化.假设有如下代码:
val1=x;//语句①
...//不使用变量x的代码
val2=x;//语句②
编译器会注意到上述代码使用了2次x,但不改变其值,因此会把x的值临时存放在寄存器中.在val2
需要x时,会从寄存器(而不是从原始内存位置上)读取x的值,这个过程称为"高速缓存"(Caching)
但如果一些代理在语句①和②之间改变了x的值,就不能这样优化了.如果没有volatile关键字,编译
器就不能确定这种事是否会发生.因此,为了安全起见,在ANSI C之前,编译器不会进行高速缓存.
现在,如果声明中没有volatile关键字,编译器会假定变量的值在使用过程中不会被代理改变,然
后再尝试代码优化

4.restrict类型限定符:

restrict限定符允许编译器优化某部分代码以更好地支持计算,只能用于指针,用于表明该指针是
访问相应数据对象的唯一且初始的方法.考虑下述代码:
int ar[10];
int * restrict restar=(int *)malloc(10*sizeof(int));
//这里,restrict说明指针restar是访问malloc()分配的内存的唯一且初始的方法
int * par=ar;
int n;
for (n=0;n<10;n++) {
    par[n]+=5;
    restar[n]+=5;
    ar[n]*=2;
    par[n]+=3;
    restar[n]+=3;
}
对restar指向的数据进行操作的2个语句可以合并为"restar[n]+=8;",但涉及par的语句不能
合并,否则会给出错误的结果(因为在这中间ar也修改了相同位置的数据)
如果未使用restrict关键字,编译器就必须考虑最坏的情况(即在2次涉及par的操作间,其他标识
符修改了数据);如果使用了restrict关键字,编译器就可以选择捷径优化计算
restrict关键字也可用于作为函数形参的指针

注意:编译器不会检查restrict变量是否遵循了限制,但违反了限制会导致结果错误.

5._Atomic限定符:

C11通过可选的头文件stdatomic.h和threads.h提供了一些可选的多线程管理方法.需要注意
的是,要通过各种宏函数来访问原子类型.1个线程对1个原子类型的对象执行原子操作时,其他线
程不能访问该对象.如下述代码:
_Atomic int hogs;//hogs是1个原子类型的变量
atomic_store(&hogs,12);//通过stdatomic.h中的宏在hogs中存储12
在这里,在hogs中存储121个原子过程,此时其他线程不能访问hogs
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页