现在的位置: 首页 > 综合 > 正文

LINUX-C成长之路(八):存储类,动态内存

2017年12月12日 ⁄ 综合 ⁄ 共 2374字 ⁄ 字号 评论关闭

C语言有几个关键字,在定义一个变量或者一个函数的时候,指定其存储区域类型,被称为存储类关键字,它们是:

static,extern,register 和 auto

下面逐一讲解。

1,static

其实这个关键字有三个作用,而不仅仅是存储类型。请看下面代码:

// 1.修饰函数,使其只能在本文件可见
static void func(void)
{
    static int n = 0; // 2. 修饰局部变量,使其存储在静态区(存储类型)
    printf("%d\n", n);
}

static int global; // 3. 修饰全局变量,使其只能在本文件可见

注意到,static在C语言中的三个作用,其中第1和第3个作用其实都是一样的,改变的是函数或者变量的可见范围。只有当用static来修饰局部变量的时候,它的作用才是代表一个存储区域。

2,extern

extern关键字用来修饰一个标识符(变量或者函数)的声明,表明该标识符在“别的地方”有定义,参考以下代码:

extern int a; // 声明一个“别处”定义的全局变量,此处extern不可省略
extern int f(); // 声明一个“别处”定义的函数,此处extern可省略

“别处”的意思仅仅指不在当前位置,可能是别的文件,也可能是该文件的其他地方。注意,如果声明的是一个变量,那只能是全局变量,局部变量不能用extern来声明。

3,register

顾名思义,register用来修饰一个希望存放在寄存器中的变量,但是这仅仅是对系统的一个建议,寄存器是一种稀缺资源,能不能满足要求要看系统当时的情况。但是,不管系统是否真的将变量存入寄存器,程序都不能对该变量进行取址运算。另外,并不是什么变量都可以定义为register类型,因为寄存器的大小跟CPU的字长相关,一般在32位系统当中,寄存器也是32位的,因此可以将小于等于32位的变量声明为register型。

被声明位register类型的变量一般是因为程序对其读写性能很敏感,比如在底层操作系统中,要频繁地对某个变量进行读写,而且这个变量的读写速度直接影响到了程序的性能,是整个程序的热点也是瓶颈,那么就很有必要将其声明为register存储型。

4,auto

auto这个关键字就像signed一样,一般都会被省略。因为auto的含义是将一个变量存储在“自动”存储区,所谓的自动存储区就是进程的栈空间,而普通的局部变量默认就是存储在 栈空间的,所以没有必要再用auto来修饰。而auto又不能修饰全局变量,因为全局变量一定是存储在静态区的。请看下面的分析:

// 所有的全局变量统统存储在静态区
int g1;
static int g2; // static 跟存储类型无关,它改变的是g2的可见范围。

int main(void)
{
    int a; // 不需要auto修饰,默认就是自动变量,存储在栈空间
    static int b; // 被static修饰,存储在静态区
}

下面是每一种变量在内存空间中的分布情况:

注意上图中的用户栈和运行时堆,它们是会在程序运行时动态变化的,里面的变量时而诞生时而消亡,具体而言指的是:在栈中存在的变量都是局部变量(包括函数的形参),其生命周期是从定义它的语句开始,到离开其作用域为止。在堆中存在的变量都是所谓的动态内存,这些内存的生命周期都是由程序员指定的,从malloc()分配某块内存开始,到free()释放这块内存结束。堆和栈分别向上和向下的箭头代表它们在内存中的增长的趋势。

相对地,读写段(即静态区)、代码段、常量区等内存区域则在程序运行期间是“稳定”的,即不会被释放,这些区域的内容将会被一直保留,直到整个程序退出为止。

这里来着重强调一下堆和栈。

对于栈而言,要明确的一点是栈空间的大小是有限的,通常来讲,对于一个32位的系统而言,一个进程的默认栈空间是8M,如果是嵌入式LINUX系统,其栈空间的大小可能经过调整会更小,比如1M。(如果是多线程,多个线程的栈空间会一字排开,但是中间用零权限内存来构建一个警戒区加以保护)。而栈空间存放的是局部变量,因此,以下情况不适合使用局部变量:

第一,巨大的变量。比如一个2000个元素的数组,那么大的数据量很有可能使得栈溢出。

第二,动态的数据。比如一个动态增长的链表,虽然程序运行初时不大,但是程序无法预料以后该链表的节点数目,像这种动态的数据也不能用栈空间来存放。

由于栈空间的分配跟释放是由系统自动完成的,其执行效率高速度快,所以适合用来存放一些可以被临时释放的数据。

而堆空间,则是程序运行时大部分数据的真正的演练场,这部分空间的分配和释放完全由程序员来控制,因此也叫做内存的自由区,自由意味着谨慎,如果只分配不释放,内存就会很快被用光,因此,堆空间是一个需要你更加负责任的地方,请看下面的代码:

char * func(void)
{
	char *p = malloc(100); // 在堆空间中申请一块100bytes的内存
	return p; // 返回后,堆内存不会因为函数的退出而释放
}

int main(void)
{
	char *k;
	k = func();
	if(k == NULL)
		return -1; // 堆空间申请失败!
	... // 堆空间申请成功,并使用这块内存
	...
	free(k); // 释放这块内存
}

动态内存的使用有几点要注意:

1,使用完毕之后,一定要释放。

2,如果不释放,那么该块内存只能等到程序全部退出之后才能被释放。

3,free() 只能释放动态内存(即由malloc( ) 或者 calloc() 或者 realloc() 分配的内存)。

4,访问动态内存的唯一方式是采用指针,因为动态内存是匿名的。

以上代码中的第3行,用一个char型指针来索引这块动态内存,是因为char型指针可以很方便地遍历内存中的每一个字节。如果这块内存是用来存放特殊的数据类型,就用需要的类型指针来遍历即可。

【上篇】
【下篇】

抱歉!评论已关闭.