C语言 存储类别、类型限定符

对象

从硬件方面来看,被储存的每个值都占用一定的物理内存,C 语言把这样的一块内存称为对象(object)。对象可以储存一个或多个值。一个对象可能并未储存实际的值,但是它在储存适当的值时一定具有相应的大小(面向对象编程中的对象指的是类对象,其定义包括数据和允许对数据进行的操作,C不是面向对象编程语言)。

从软件方面来看,程序需要一种方法访问对象。这可以通过声明变量来完成。

标识符

标识符是一个名称,可以用来指定特定对象的内容。标识符遵循变量命名规则。

可以用存储期描述对象;可以用作用域和链接描述标识符。

存储期是指对象在内存中保留了多长时间。

作用域

描述程序中可访问标识符的区域。包括块作用域、函数作用域、函数原型作用域、文件作用域。

块作用域

定义在块中的变量具有块作用域(block scope),块作用域变量的可见范围是从定义处到包含该定义的块的末尾。另外,虽然函数的形式参数声明在函数的左花括号之前,但是它们也具有块作用域,属于函数体这个块。所以到目前为止,我们使用的局部变量(包括函数的形式参数)都具有块作用域。

以前,具有块作用域的变量都必须声明在块的开头,C99标准放宽了这一限制,允许在块中的任意位置声明变量。

函数作用域

仅用于goto语句的标签。这意味着即使一个标签首次出现在函数的内层块中,它的作用域也延伸至整个函数。如果在两个块中使用相同的标签会很混乱,标签的函数作用域防止了这样的事情发生。

函数原型作用域

用于函数原型中的形参名(变量名)。

函数原型作用域的范围是从形参定义处到原型声明结束。这意味着,编译器在处理函数原型中的形参时只关心它的类型,而形参名(如果有的话)通常无关紧要。而且,即使有形参名,也不必与函数定义中的形参名相匹配。只有在变长数组中,形参名才有用。

void use_a_VLA(int n,int m,ar[n][m]);

方括号中必须使用在函数原型中已声明的名称。

文件作用域

变量的定义在函数的外面,具有文件作用域(file scope)。具有文件作用域的变量,从它的定义处到该定义所在文件的末尾均可见。

链接

C变量有种链接属性:外部链接、内部链接或无连接。具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量。这意味着这些变量属于定义它们的块、函数或原型私有。具有文件作用域的变量可以是外部链接或内部链接。外部链接变量可以在多文件程序中使用,内部链接变量只能在一个翻译单元中使用。

正式和非正式术语

C 标准用“内部链接的文件作用域”描述仅限于一个翻译单元(即一个源代码文件和它所包含的头文件)的作用域,用“外部链接的文件作用域”描述可延伸至其他翻译单元的作用域。但是,对程序员而言这些术语太长了。一些程序员把“内部链接的文件作域”简称为“文件作用域”,把“外部链接的文件作用域”简称为“全局作用域”或“程序作用域”。

如何知道文件作用域变量是内部链接还是外部链接?

可以查看外部定义中是否使用了存储类别说明符static。

存储期

作用域和链接描述了标识符的可见性,存储期描述了通过这些标识符访问的对象的生存期。

C有四种存储期:静态存储期、线程存储期、自动存储期、动态分配存储期。

静态存储期

如果对象具有静态存储期,那么它在程序的执行期间一直存在。文件作用域变量具有静态存储期。注意,对于文件作用域变量,关键字 static表明了其链接属性,而非存期。以static声明的文件作用域变量具有内部链接。但是无论是内部链接还是外部链接,所有的文件作用域变量都具有静态存储期。

线程存储期

线程存储期用于并发程序设计,程序执行可被分为多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。

自动存储期

块作用域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出这个块时,释放刚才为变量分配的内存。这种做法相当于把自动变量占用的内存视为一个可重复使用的工作区或暂存区。例如,一个函数调用结束后,其变量占用的内存可用于储存下一个被调用函数的变量。

变长数组稍有不同,它们的存储期从声明处到块的末尾,而不是从块的开始处到块的末尾。

我们到目前为止使用的局部变量都是自动类别。

然而,块作用域变量也能具有静态存储期。为了创建这样的变量,要把变量声明在块中,且在声明前面加上关键字static。

C语言 存储类别、类型限定符_第1张图片

自动变量

属于自动存储类别的变量具有自动存储期、块作用域且无连接。

默认情况下,声明在块或函数头中的任何变量都属于自动存储类别。为了更清楚地表达你的意图(例如,为了表明有意覆盖一个外部变量定义,或者强调不要把该变量改为其他存储类别),可以显式使用关键字auto。

int main(void)

{

auto int plox;

关键字auto是存储类别说明符(storage-class specifier)。auto关键字在C++中的用法完全不同,如果编写C/C++兼容的程序,最好不要使用auto作为存储类别说明符。

块作用域和无链接意味着只有在变量定义所在的块中才能通过变量名访问该变量(当然,参数用于传递变量的值和地址给另一个函数,但是这是间接的方法)。另一个函数可以使用同名变量,但是该变量是储存在不同内存位置上的另一个变量。

变量具有自动存储期意味着,程序在进入该变量声明所在的块时变量存在,程序在退出该块时变量消失。原来该变量占用的内存位置现在可做他用。

如果内层块中声明的变量与外层块中的变量同名会怎样?

内层块会隐藏外层块的定义。但是离开内层块后,外层块变量的作用域又回到了原来的作用域。

自动变量不会初始化,除非显示初始化它。可以用非常量表达式初始化自动变量,前提是所用的变量已在前面定义过。

寄存器变量

由于寄存器变量储存在寄存器而非内存中,所以无法获取寄存器变量的地址。绝大多数方面,寄存器变量和自动变量都一样。也就是说,它们都是块作用域、无链接和自动存储期。使用存储类别说明符register便可声明寄存器变量。

块作用域的静态变量

实际上,静态的意思是该变量在内存中原地不动,并不是说它的值不变。具有文件作用域的变量自动具有(也必须是)静态存储期。前面提到过,可以创建具有静态存储期、块作用域的局部变量。这些变量和自动变量一样,具有相同的作用域,但是程序离开它们所在的函数后,这些变量不会消失。也就是说,这种变量具有块作用域、无链接,但是具有静态存储期。计算机在多次函数调用之间会记录它们的值。在块中(提供块作用域和无链接)以存储类别说明符static(提供静态存储期)声明这种变量。

“局部静态变量”是描述具有块作用域的静态变量的另一个术语。阅读一些老的C文献时会发现,这种存储类别被称为内部静态存储类别(internal static storage class)。这里的内部指的是函数内部,而非内部链接。

外部链接的静态变量

外部链接的静态变量具有文件作用域、外部链接和静态存储期。该类别有时称为外部存储类别,属于该类别的变量称为外部变量。把变量的定义性声明放在在所有函数的外面便创建了外部变量。当然,为了指出该函数使用了外部变量,可以在函数中用关键字extern再次声明。如果一个源代码文件使用的外部变量定义在另一个源代码文件中,则必须用extern在该文件中声明该变量。

初始化外部变量

外部变量和自动变量类似,也可以被显式初始化。与自动变量不同的是,如果未初始化外部变量,它们会被自动初始化为 0。这一原则也适用于外部定义的数组元素。与自动变量的情况不同,只能使用常量表达式初始化文件作用域变量。

内部链接的静态变量

该存储类别的变量具有静态存储期、文件作用域和内部链接。在所有函数外部(这点与外部变量相同),用存储类别说明符static定义的变量具有这种存储类别。

这种变量过去称为外部静态变量(external static variable),但是这个术语有点自相矛盾(这些变量具有内部链接)。但是,没有合适的新简称,所以只能用内部链接的静态变量(static variable with internal linkage)。普通的外部变量可用于同一程序中任意文件中的函数,但是内部链接的静态变量只能用于同一个文件中的函数。可以使用存储类别说明符 extern,在函数中重复声明任何具有文件作用域的变量。这样的声明并不会改变其链接属性。

存储类别说明符

C语言中,有六个关键字作为存储类别说明符:auto、register、static、extern、_Thread_local和typedef。

typedef

typedef关键字与任何内存存储无关,把它归于此类有一些语法上的原因。尤其是,在绝大多数情况下,不能在声明中使用多个存储类别说明符,所以这意味着不能使用多个存储类别说明符作为typedef的一部分。唯一例外的是_Thread_local,它可以和static或extern一起使用。

auto

auto说明符表明变量是自动存储期,只能用于块作用域的变量声明中。由于在块中声明的变量本身就具有自动存储期,所以使用auto主要是为了明确表达要使用与外部变量同名的局部变量的意图。

register

register 说明符也只用于块作用域的变量,它把变量归为寄存器存储类别,请求最快速度访问该变量。同时,还保护了该变量的地址不被获取。

static

用 static 说明符创建的对象具有静态存储期,载入程序时创建对象,当程序结束时对象消失。如果static 用于文件作用域声明,作用域受限于该文件。如果 static 用于块作用域声明,作用域则受限于该块。因此,只要程序在运行对象就存在并保留其值,但是只有在执行块内的代码时,才能通过标识符访问。块作用域的静态变量无链接。文件作用域的静态变量具有内部链接。

extern

extern 说明符表明声明的变量定义在别处。如果包含 extern 的声明具有文件作用域,则引用的变量必须具有外部链接。如果包含 extern 的声明具有块作用域,则引用的变量可能具有外部链接或内部链接,这接取决于该变量的定义式声明。

存储类别

自动变量具有块作用域、无链接、自动存储期。它们是局部变量,属于其定义所在块(通常指函数)私有。寄存器变量的属性和自动变量相同,但是编译器会使用更快的内存或寄存器储存它们。不能获取寄存器变量的地址。

具有静态存储期的变量可以具有外部链接、内部链接或无链接。在同一个文件所有函数的外部声明的变量是外部变量,具有文件作用域、外部链接和静态存储期。如果在这种声明前面加上关键字static,那么其声明的变量具有文件作用域、内部链接和静态存储期。如果在函数中用 static 声明一个变量,则该变量具有块作用域、无链接、静态存储期。

具有自动存储期的变量,程序在进入该变量的声明所在块时才为其分配内存,在退出该块时释放之前分配的内存。如果未初始化,自动变量中是垃圾值。程序在编译时为具有静态存储期的变量分配内存,并在程序的运行过程中一直保留这块内存。如果未初始化,这样的变量会被设置为0。

具有块作用域的变量是局部的,属于包含该声明的块私有。具有文件作用域的变量对文件(或翻译单元)中位于其声明后面的所有函数可见。具有外部链接的文件作用域变量,可用于该程序的其他翻译单元。具有内部链接的文件作用域变量,只能用于其声明所在的文件内。

存储类别和函数

函数的存储类别:外部函数(默认)、静态函数和C99新增的内联函数。

通常的做法是:用 extern 关键字声明定义在其他文件中的函数。这样做

是为了表明当前文件中使用的函数被定义在别处。除非使用static关键字,

否则一般函数声明都默认为extern。

存储类别的选择

通常的做法是:用 extern 关键字声明定义在其他文件中的函数。这样做是为了表明当前文件中使用的函数被定义在别处。除非使用static关键字,否则一般函数声明都默认为extern。

本地查找

#include" "

把文件名放在双引号中而不是尖括号中,指示编译器在本地查找文件,而不是到编译器存放标准头文件的位置去查找。

“本地查找”的含义取决于具体的实现。一些常见的实现把头文件与源代码文件或工程文件(如果编译器使用它们的话)放在相同的目录或文件夹中。(如果这个文件是自己编写的,并且放在项目目录内,就是本地)

ANSI C类型限定符

const 恒常性

初始化后就不能改变它的值。

const int nochange=12; //正确

const int nochange; nochange=12; //错误

在指针中使用const

重点:区分是限定指针本身为const还是限定指针指向的值为const

const float *pf;

float const *pf;const在 * 前面,限定指针指向的值为const,即pf指向不能被改变的值,而pf本身的值是可以改变的,可以设置该指针指向其它const值。

float *const pt; const在 * 后面,pt是一个const指针,pt本身的值不能改变,pt必须指向同一个地址,它所指向的值可以改变

const float *const ptr; ptr既不能指向别处,它所指向的值也不能改变

对全局数据使用const

目的:使用全局变量,在程序的任何部分都可以更改数据

一、遵循外部变量的常用规则,即在一个文件中使用定义式声明,其他文件中使用引用式声明(使用extern关键字)

const double PI=3.14159;

const char *MONTHS[12]={"Jan","Feb","Mar","Apr","May","June","July","Aug","Sep","Oct","Nov","Dec"};

/*定义式声明*/

extern const double PI;

extern const *MONTHS[,]; /*引用式声明*/

二、把const变量放在一个头文件中,然后再其他文件中包含该头文件

static const double PI=3.14159;

static const char *MONTHS[12]={"Jan","Feb","Mar","Apr","May","June","July","Aug","Sep","Oct","Nov","Dec"}; /*定义一些外部const变量*/

#include "constant.h" /*使用定义在别处的外部const变量*/

volatile 易变性

遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对其地址的稳定访问。如果不使用volatile,则编译器将对所声明的语句进行优化。

restrict

适用范围:指针,表示该指针是访问数据对象的唯一且初始的方式

int ar[10];

int * restrict restar=(int *) malloc(10 * sizeof(int));

int * par=ar;

因为指针restar是访问由malloc()所分配内存的唯一且初始的方式,所以可以使用restrict关键字限定它。指针par既不是访问ar数组中数据的初始方式,也不是唯一方式,所以不用把它设置为restrict。

作用

一、告诉编译器,对特定的空间只能用特定的一个指针进行操作,方便操作方面的优化(顺便优化)

二、告诉读者,一个函数中有多个该限定符修饰的指针,这些指针之间的存储空间不能重叠,如果重叠就意味着这些指针可以同时操作同一个空间(指针分配的存储空间不能有重叠的部分)

memcpy()

头文件:string.h(C) /cstring(C++)

函数原型:void * memcpy(void * restrict s1,const void * restrict s2,size_t n)

参数:

s1-指向用于存储复制内容的目标数组,类型强制转换为void*指针

s2-指向要复制的数据源,类型强制转换为void*指针

n-要被复制的字节数

返回值:返回一个指向目标存储取s1的指针

功能:从s2所指的内存地址的起始位置开始拷贝n个字节到目标s1所指的内存地址的起始位置中

C语言 存储类别、类型限定符_第2张图片

_Atomic

要通过各种宏函数来访问原子类型。当一个线程对一个原子类型的对象执行原子操作时,其他线程不能访问该对象。

【线程(一个程序内同时进行的操作):操作系统能够进行运算调度的最小单位,被包含再进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。原子操作:该操作绝不会在执行完毕前被任何其他任务或事件打断,没有比它的执行单位更小的执行单位了。原子操作分为位和整型变量两类原子操作。原子操作函数分为整形原子操作和位原子操作。】

int hogs;

hogs=12;

可替换为:

_Atomic int hogs; //hogs是一个原子类型的变量

atomic_store(&hogs,12); //stdatomic.h中的宏,hogs存储12是一个原子过程,其他线程不能访问hogs

你可能感兴趣的:(C语言,c语言)