在计算机系统,特别是嵌入式系统中,内存资源是非常有限的。尤其对于移动端开发者来说,硬件资源的限制使得其在程序设计中首要考虑的问题就是如何有效地管理内存资源。本文是作者在学习C语言内存管理的过程中做的一个总结,如有不妥之处,望读者不吝指正。
因为不同的编译器和平台,对于内存的管理(段的划分)不尽相同,所以这里以 Linux 为参考总结C语言的内存管理
几个基本概念
在C语言中,关于内存管理的知识点比较多,如函数、变量、作用域、指针等,在探究C语言内存管理机制时,先简单复习下这几个基本概念:
变量
不解释。但需要搞清楚这几种变量类型:
局部变量(自动变量)
一般情况下,代码块{}
内部定义的变量就是自动变量,也可使用auto
显示定义。
auto只能用来标识局部变量的存储类型,对于局部变量,auto是默认的存储类型,不需要显示的指定。因此,auto标识的变量存储在栈区中。示例如下:
#include <stdio.h>
int main(void)
{
auto int i=1; //显示指定变量的存储类型
int j=2;
printf("i=%d\tj=%d\n",i,j);
return 0;
}
全局变量(外部变量)
出现在代码块{}
之外的变量就是全局变量。
extern
注意:extern
修饰变量时,根据具体情况,既可以看作是定义也可以看作是声明;但extern修饰函数时只能是定义,没有二义性。
当extern
用于定义的时候,用来声明在当前文件中引用在当前项目中的其它文件中定义的全局变量(必须是全局变量)。如果全局变量未被初始化,那么将被存在BBS区(存放的是未初始化的全局变量和静态变量)中,且在编译时,自动将其值赋值为0,如果已经被初始化,那么就被存在数据区中。
全局变量,不管是否被初始化,其生命周期都是整个程序运行过程中,为了节省内存空间,在当前文件中使用extern来声明其它文件中定义的全局变量时,就不会再为其分配内存空间。
例如:
file.c文件
#include <stdio.h>
int i=5; //定义全局变量,并初始化
void test(void)
{
printf("in subfunction i=%d\n",i);
}
test.c文件
#include <stdio.h>
extern i; //声明引用全局变量i
int main(void)
{
printf("in main i=%d\n",i);
test();
return 0;
}
编译并运行:
gcc -o test test.c file.c #编译连接
./test #运行
结果:
结果:
in main i=5
in subfunction i=5
extern关键字只需要指明类型和变量名就行了,不能再重新赋值,初始化需要在原文件所在处进行,如果不进行初始化的话,全局变量会被编译器自动初始化为0。像这种写法是不行的。
extern int num=4;
但是在声明之后就可以使用变量名进行修改了,像这样:
#include<stdio.h>
int main()
{
extern int num;
num=1;
printf("%d",num);
return 0;
}
如果不想这个变量被修改可以使用const关键字进行修饰,写法如下:
mian.c
#include<stdio.h>
int main()
{
extern const int num;
printf("%d",num);
return 0;
}
b.c
#include<stdio.h>
const int num=5;
void func()
{
printf("fun in a.c");
}
使用include将另一个文件全部包含进去可以引用另一个文件中的变量,但是这样做的结果就是,被包含的文件中的所有的变量和方法都可以被这个文件使用,这样就变得不安全,如果只是希望一个文件使用另一个文件中的某个变量还是使用extern关键字更好。
extern除了引用另一个文件中的变量外,还可以引用另一个文件中的函数,引用方法和引用变量相似。
mian.c
#include<stdio.h>
int main()
{
extern void func();
func();
return 0;
}
b.c
#include<stdio.h>
const int num=5;
void func()
{
printf("fun in a.c");
}
这里main函数中引用了b.c中的函数func。因为所有的函数都是全局的,所以对函数的extern用法和对全局变量的修饰基本相同,需要注意的就是,需要指明返回值的类型和参数。
静态变量
被声明为静态类型的变量,无论是全局的还是局部的,都存储在数据区中,其生命周期为整个程序,如果是静态局部变量,其作用域为一对{}内,如果是静态全局变量,其作用域为当前文件。静态变量如果没有被初始化,则自动初始化为0。静态变量只能够初始化一次。
**总结:**用static声明局部变量,使其变为.data或者.bss段,作用域不变;用static声明外部变量,其本身就是静态变量,这只会改变其连接方式,使其只在本文件内部有效,而其他文件不可连接或引用该变量。
#include <stdio.h>
int sum(int a)
{
auto int c=0;
static int b=5;
c++;
b++;
printf("a=%d,\tc=%d,\tb=%d\t",a,c,b);
return (a+b+c);
}
int main()
{
int i;
int a=2;
for(i=0;i<5;i++)
printf("sum(a)=%d\n",sum(a));
return 0;
}
运行结果
$ gcc -o test test.c
$ ./test
a=2, c=1, b=6 sum(a)=9
a=2, c=1, b=7 sum(a)=10
a=2, c=1, b=8 sum(a)=11
a=2, c=1, b=9 sum(a)=12
a=2, c=1, b=10 sum(a)=13
字符串常量
字符串常量存储在**常量数据(.rodata段)**中,其生存期为整个程序运行时间,但作用域为当前文件,示例如下:
#include <stdio.h>
char *a="hello";
void test()
{
char *c="hello";
if(a==c)
printf("yes,a==c\n");
else
printf("no,a!=c\n");
}
int main()
{
char *b="hello";
char *d="hello2";
if(a==b)
printf("yes,a==b\n");
else
printf("no,a!=b\n");
test();
if(a==d)
printf("yes,a==d\n");
else
printf("no,a!=d\n");
return 0;
}
运行结果:
$ gcc -o test test.c
$ ./test
yes,a==b
yes,a==c
no,a!=d
register存储类型
声明为register的变量在由内存调入到CPU寄存器后,则常驻在CPU的寄存器中,因此访问register变量将在很大程度上提高效率,因为省去了变量由内存调入到寄存器过程中的好几个指令周期。如下示例:
#include <stdio.h>
int main(void)
{
register int i,sum=0;
for(i=0;i<10;i++)
sum=sum+1;
printf("%d\n",sum);
return 0;
}
常见问题
static全局变量与普通的全局变量有什么区别?
答:全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别在于作用域的扩展上。非静态的全局变量可以用extern扩展到组成源程序的多个文件中,而静态的全局变量的作用域只限于本文件,不能扩展到其它文件,由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。把全局变量改变为静态全局变量后是改变了它的作用域,限制了它的使用范围。
static局部变量和普通局部变量有什么区别?
答:把局部变量改变为静态局部变量后是改变了它的存储方式即改变了它的生存期。
static函数与普通函数有什么区别?
答:static函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static)