存储类别、链接和内存管理
1. 预备知识
1.1 作用域
块作用域
块作用域也称为局部作用域,也就是语句块的作用域。一般在函数中起到分割的作用。
{
int a; //a的作用域起始处
scanf("%d", &a);
if(a < 0){ //b的作用域起始处
int b;
b -= a;
} //b的作用域结束处
} //a的作用域结束处
函数原型作用域
它指的是在声明函数原型时所指定的参数标识符的作用范围。因为作用范围是小括号内,所以函数原型声明中的标识符可以与函数定义中说明的标识符名称不同。只要让函数声明和函数定义中小括号内每个变量的类型及数目一致即可,也可以省略掉参数名(函数定义在后,调用在前)
double max(double x, double y);
double max(double, double);
//两者均可
- 当标识符的作用域完全相同时,不允许出现相同的标识符名,而当标识符有不同作用域时允许标识符同名。
- 如果是作用域嵌套的情况下,如果内层和外层的作用域声明了同名的标识符,那么在外层作用域中声明的标识符对于该内层作用域时不可见的。也就是说,在内层中声明的变量i和外层变量i无关,当内层变量改变时,与之同名的外层变量的值不受影响。(在内层重定义时可以使用,要不然会改变外层变量的值)
Void fun(){
int i = 0;
{
int i = 2;
printf(“i2 = %d”, i); //i的值为2
i += 2;
}
printf(“i1 = %d”, i); //i的值为0
}
函数作用域
在函数内部定义的一些变量,只能在函数内部使用。一旦离开了这个函数,就必须重新定义。可以把函数想成一个块。然后套用块作用域的定义。
文件作用域
在所有函数外定义的标识符称为全局标识符,定义的变量称为全局变量。全局标识符的作用域是文件作用域,即从它声明开始到文件结束都是可见的。标识符的文件作用域一般有以下三种情况。
- 全局常量或全局变量的作用域是从定义开始到源程序文件结束。
const float PI = 3.14;
int a = 0;
int main(){
.......
}
- 因为函数的定义中包含了函数声明,所以一旦声明了函数原型,函数标识符的作用域就从定义开始到源程序文件结束。
void funA(int x); //函数funA的作用域从此开始到文件结束
void funB(){ //函数funB的作用域从此开始到文件结束
/*...*/
}
int main(){
/*.....*/
}
void funA(int x){
/*......*/
}
- 对于在头文件中定义的标识符,当她们被预编译时,会将头文件的内容在源文件的相应位置展开,所以在头文件中定义的标识符的作用域可以看成从#include头文件开始的位置到源程序文件结束。
例如:stdio.h里的printf和scanf的作用域就是从#include预处理指令开始一直到源程序文件结束。
1.2 链接
内部链接
用static修饰的变量或者函数的链接属性为其作用领域只能仅限在本文件中 ,在其他文件中就不能进行调用,不同文件中的内部函数是不会相互干扰的
- 头文件
#ifndef __STATIC_DEMO_H_
#define __STATIC_DEMO_H_
int add(int a, int b);
#endif
- 实现
#include "staticDemo.h"
#include<stdio.h>
//静态static修饰的函数声明,只能在staticDemo中去使用
static void out_result(int);
//静态static修饰的变量,只能在本文件中使用,外部文件无法访问
static int num = 50;
//缺省默认是extern修饰的,外部是可以进行访问的
int add(int a,int b){
int plus = a + b;
out_result(plus);
return plus;
}
static void out_result(int result){
printf("out_result:%d\n",result);
}
外部链接
允许外部文件调用非本文件的函数或者变量,或者在本文件中,函数执行在定义之前的时候,使用extern进行提前声明的话,编译时候不会报错
- 头文件
#ifndefine __EXTERNAL_DEMO_H_
#define __EXTERNAL_DEMO_H
//在头文件声明的时候用extern来修饰
external int add(int a,int b);
#endif
- 实现
#include "external_h.h"
//在文件内部进行使用,外部文件不能访问
static int num = 20;
//用于外部文件调用,用extern来修饰
extern int add(int a, int b){
return a+b;
}
无连接
具有块作用域、函数作用域或函数原型作用域的变量都是无连接变量。这意味着这些变量属于定义他们的块、函数或原型私有。
1.3 存储期
静态存储期
一个对象具有静态存储期,则它在程序运行的执行期间一直存在
块作用域的变量加上static关键字就可以具有静态存储期
线程存储期
用于并发程序设计,程序执行时被分为多个线程,具有线程存储期的对象,从被声明时到线程结束一直存在
自动存储期
当程序进入定义这些变量的块时,为这些变量分配内存,当退出这个块时,释放方才为变量分配的内存。
块作用域的变量通常具有自动存储期
动态分配存储期
2. 存储类别
2.1 自动变量
除非未指定,否则函数中定义的所有局部变量都称为自动 变量,即默认情况下,局部变量是自动变量。 除非手动初始化,否则不会被自动初始化。在声明局部变量时,无需放置关键字auto (它是可选的)。每次调用函数(在其中声明变量)时都会创建一个新的自动变量,并在程序执行离开该函数时销毁该变量。
2.2 寄存器变量
通常,变量存储在计算机内存中。幸运的话可以存储在CPU寄存器中,或者跟一般的,存储在速度最快的可用内存中,从而可以比普通变量更快的访问。但是你申请寄存器变量,系统在权衡后,可能不会允许。
声明一个变量为寄存器变量只需要加register就可以了。
2.3 块作用域的静态变量
静态变量(static variable)。这些变量和自动变量具有相同的作用域,但当包含这些变量的函数完成工作时,他们并不消失。
注意:对函数内的参量不能使用static定义。
2.4 外部链接的静态变量
external variable 把变量的定义放在所有函数之外,创建了一个外部变量。为了使程序更加清晰,可以在使用外部变量的函数中通过使用extern关键字来再次声明他,如果是在别的文件中声明的,那extern就是必须的。
int Errupt; /*外部定义的变量*/
double up[100]; /*外部定义的数组*/
extern char coal;
/*如果coal被定义在另一个文件,则必须这样声明*/
void next(void);
int main(void)
{
extern int Errupt; /*可选的声明*/
extern double up[]; /*可选的声明*/
...
}
2.5 内部链接的静态变量
这个与全局变量的区别在于声明时需要加上static关键字,同时无法被其他文件使用.自动初始化为0.
3. 存储类别和函数
函数也有存储类别,可以是外部函数(默认)或静态函数。C99新增了内联函数。外部函数可以被其他文件的函数访问,但是静态函数只能用于其定义所在的文件。
4. 分配内存
4.1 malloc()和free()
通过声明一个指针,调用malloc(),将其返回值赋给指针,使用指针访问数组元素的方法,创建动态数组
#include <stdio.h>
#include <stdlib.h>
int main()
{
int * ptd;
int max;
scanf("%d",&max);
ptd = (int *)malloc(max * sizeof(int));
...
free(ptd); /*避免内存泄露*/
return 0;
}
4.2 calloc()
long * n;
n = (long *)calloc(100,sizeof(long));
/*创建了一百个存储单元*/
4.3 存储类别和动态内存分配
理想化模型(认为程序把他可用的内存分为三部分):
- 供具有外部链接、内部链接和无连接的静态变量使用
在编译时确定,只要程序还在运行,就可以访问储存在该部分的数据。该类型的变量在程序开始执行时被创建,在程序结束时被销毁
- 供自动变量使用
在程序进入变量定义所在块时存在,程序离开块时消失。因此,随着调用函数和函数的结束,自动变量所用的内存数量也相应地增加和减少。这部分内存通常作为栈来处理,新创建的变量按顺序加入内存,以相反的顺序销毁
- 供动态内存分配
由程序员管理,在调用malloc()或相关函数时存在,在调用free()函数后释放。
5. ANSI C类型限定符
5.1 const类型限定符
以const类型关键字声明的对象,其值不能通过赋值来递增,递减来修改。
在形参和指针声明中使用const
- 创建了pf指向的值不能被改变,而pf本身的值可以改变。例如,可以设置该指针指向其它const值
const float *pf;//pf指向一个float类型的const值
- 创建的指针pt本身不能改变,pt必须指向同一地址,但是它所指向的值可以改变。
float * const pt;//pt是一个const指针
- 表明ptr既不能指向别处,它所指向的值也不能改变。
const * float * const ptr;
把const放在类型名后,之前,说明该指针不能改变它所指向的值。
简而言之,const放在左侧的任意位置,限定了指针指向的数据不能改变;
const放在*的右侧,限定了指针本身不能改变
对全局数据使用const
使用全局变量是一种冒险的方法,因为这样做暴露了数据,程序的任何部分都能更改数据,但如果把数据设置为const,就可以避免这样的危险,因此使用const限定符声明全局变量很合理,然而,在文件间共享const要小心,可以采用两个策略:
- 在一个文件中采用定义式声明,另一个文件采用引用式声明:
/*file1.c---定义了一些外部const变量*/
const double PI = 3.1415926;
const char *MONTHS [12] = {1,2,3,4,5,6,7,8,9,10,11,12};
/*file2.c---使用定义在别处的外部const变量*/
extern const double PI;
extern const char *MONTHS[];
- 把const变量放在另一个头文件中,然后在其它头文件中包含该头文件:
/*constant.h--定义了一些外部const变量*/
static const double PI = 3.1415926;
static const char * MONTHS = {1,2,3,4,5,6,7,8,9,10,11,12};
/*file1.c---使用定义在别处的外部const变量*/
#include "constant.h"
/*file2.c---使用定义在别处的外部const变量*/
#include "constant.h"
5.2 volatile类型限定符
volatile通常用于硬件地址以及在其他程序或多个同时运行的线程中共享数据.
volatile本意为易变的,表明代理(非变量所在程序)可以改变该变量的值。通常编译器为了优化减少读取内存会将变量由内存读入寄存器。但如果使用volatile声明变量时,系统总是重新内存读取数据,即不进行优化。如果不适用volatile,则编译器可能会声明语句进行优化。简单说就是volatile关键字影响编译器编译结果,volatile声明的变量表明该变量随时可能变化,相关运算不要优化,以免出错。
val1 = x;
/* 一些不适应x的代码 */
val2 = x;
智能的(进行优化的)编译器会注意到以上代码使用了两次x,但并未改变它的值。于是编译器把x的值临时储存在寄存器中,然后val2使用x时,从寄存器中(而不是原始内存位置上)读取x的值,以节约时间。这个过程被称为高速缓存(caching)。通常,高速缓存是个不错的优化方案,但是如果一些其他代理在以上两条语句之间改变了x的值,就不能这样优化了。如果没有volatile关键字,编译器会假定变量的值在使用过程中不变,尝试优化代码。
5.3 restrict类型限定符(C99)
restrict关键字允许编译器优化某部分代码以更好的支持计算。它只能用于指针,表明该指针是访问数据对象的唯一且初始的方法。
int ar[10];
int * restrict restar = (int *) malloc (10 * sizeof(int));
int *par = ar;
这里,指针restar是访问由malloc()所分配内存的唯一且初始的方式。因此,可以用restrict关键字限定它。而指针par既不是访问ar数组中数据的初始方式,也不是唯一方式。所以不用把它设置为restrict.
restrict关键字有两个读者。一个是编译器,该关键字告知编译器可以自由假定一些优化方案。另一个读者是用户,该关键字告知用户要使用满足restrict要求的参数。总而言之,编译器不会检查用户是否遵循这一限制,但是无视它后果自负。
5.4 _Atomic类型限定符(C11)
并发程序设计把程序执行分成可以同时执行的多个线程。这个程序设计带来了新的挑战,包括如何管理访问相同数据的不同线程。C11通过包含可选的头文件stdatomic.h和threads.h ,提供了一些可选的(不是必须实现的)管理方法。值得注意的是,要通过各种宏函数来访问院子类型。当一个线程对一个原子类型的对象执行原子操作时,其他线程不能访问该队形。例如,下面的代码:
int hogs; // 普通声明
hogs = 12; // 普通赋值
// 替换为
_Atomic int hogs; // hogs是一个原子类型的变量
atomic_store(&hogs, 12); // stdatomic.h 中的宏
这里,在hogs中储存12是一个原子过程,其他线程不能访问hogs。