C语言的特色之一是:程序员必须亲自处理内存的分配细节。
C语言使用栈(Stack)来保存函数返回地址/栈祯基址、完成函数的参数传递和函数局部变量的存储。 如果程序需要在运行的过程中动态分配内存,可以利用堆(Heap)来实现。
基本上C程序的元素存储在内存的时候有3种分配策略:
静态分配
如果一个变量声明为全局变量或者是函数的静态变量,这个变量的存储将使用静态分配方式。静态分配的内存一般会被编译器放在数据段或代码段来存储,具体取决于实现。静态分配内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,这样做的前提是,在编译时就必须确定变量的大小。
例如IA32的x86平台及gcc编译器,全局及静态变量放在数据段的低端;全局及静态常量放在代码段的高端。
自动分配
函数内局部变量的存储单元都可以在栈上创建,函数的自动局部变量随着函数的返回(函数执行结束)会自动释放(失效),这个局部变量一般都是利用栈(Stack)来满足的。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。相比于静态分配,不必绝对要求这个变量在编译时就必须确定变量的大小,运行时才决定也不迟,但是C89仍然要求在编译时就要确定,而C99放松了这个限制。但无论是C89还是C99,都不允许一个已经分配的自动变量运行时改变大小。
所以说C函数永远不应该返回一个局部变量的地址。
要指出的是,自动分配也属于动态分配,甚至可以用alloca函数来像分配堆(Heap)一样进行分配,而且释放是自动的。
动态分配
另外一种更加特殊的情况,变量的大小在运行时有可能改变,或者虽然单个变量大小不变,变量的数目却有很大弹性,不能静态分配或者自动分配,这时候可以使用堆(Heap)来满足要求。ANSI C定义的堆操作函数是malloc、calloc、realloc和free。
使用堆(Heap)内存将带来额外的开销和风险。
请看下面的程序的地址分配:
#include
#include
#include
int a = 0; //全局初始化区
char *p1; //全局未初始化区
int main()
{
int b;
char s[] = "abc";
char *p2;
char *p3 = "123456";
static int c =0;
p1 = (char *)malloc(4);
p2 = (char *)malloc(16);//分配得来的就在堆区。
fprintf(stdout, "stack\n");
fprintf(stdout, "&b: %p\n", &b);
fprintf(stdout, "&s: %p\n", &s);
fprintf(stdout, "&p2: %p\n", &p2);
fprintf(stdout, "&p3: %p\n", &p3);
fprintf(stdout, "\nheap\n");
fprintf(stdout, "p1: %p\n", p1);
fprintf(stdout, "p2: %p\n", p2);
fprintf(stdout, "p3: %p\n", p3);
strcpy(p1, "123456"); //"123456"存放常量区,编译器优化使其与p3所指向的"123456"在一个地方。
fprintf(stdout, "*p1: %p\n", *p1);
fprintf(stdout, "\nmain: %p\n", main);
fprintf(stdout, "\nstatic & global\n");
fprintf(stdout, "&a: %p\n", &a);
fprintf(stdout, "&p1: %p\n", &p1);
fprintf(stdout, "&c: %p\n", &c);
fprintf(stdout, "const\n");
fprintf(stdout, "&\"abc\": %p\n", &"abc");
fprintf(stdout, "&\"123456\": %p\n", &"123456");
return 0;
}