在阐述C语言内存布局之前,首先我们来介绍一下C语言变量的类型;在C语言中的变量类型有自动变量,全局变量,静态变量以及常量.
自动变量
自动变量又称为局部变量,默认用关键字auto修饰,但auto关键字通常会省略;一般在函数内部定义的变量都是局部变量,其作用域仅限于函数体内部,离开函数体就会被系统自动回收销毁.
int main(int argc, const char * argv[]) {
@autoreleasepool {
//均为局部变量
auto int a = 100;
int b = 200;
int c = 300;
}
return 0;
}
全局变量
一般在函数体外部定义的变量称之为全局变量,其作用域是整个应用程序.
//全局变量
int m = 1000;
int n = 2000;
int main(int argc, const char * argv[]) {
@autoreleasepool {
//均为局部变量
auto int a = 100;
int b = 200;
int c = 300;
}
return 0;
}
静态变量
- 静态局部变量: 在函数体内部,用static关键字修饰的变量称之为静态局部变量,其作用域仅限于函数体内部,外界不能访问,但其生命周期与整个应用程序相同.
- 静态全局变量: 在函数体外部,用static关键字修饰的变量称之为静态全局变量,其作用域是全局的,其生命周期与整个应用程序相同.
void Test(void);
void Test()
{
//静态局部变量
static int k = 3;
//局部变量
int m = 0;
k++;
m++;
printf(" k = %d -- m = %d\n",k,m);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Test();
Test();
Test();
}
return 0;
}

Test函数执行了三次,但静态局部变量的值在依次递增,其赋值操作好像失效了?
静态局部变量是在编译时赋初始值,并且只赋一次初始值,在以后每次调用函数时,只是使用上一次函数被调用结束时变量的值.
//静态全局变量
static int a = 200;
int main(int argc, const char * argv[]) {
@autoreleasepool {
printf(" a = %d\n",a);
a = 500;
a++;
printf(" a = %d\n",a);
}
return 0;
}

静态全局变量,在应用程序运行的过程中可以被赋值多次.
常量
- 常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量
- 常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。
- 常量就像是常规的变量,只不过常量的值在定义后不能进行修改。
整型常量
12,134,1234;
浮点型常量
12.34,15.56;
字符型常量
'a','b','c';
字符串常量
"abcd","qwerty";
常量的定义
- 使用 #define 预处理命令;
- 使用 const 关键字
#define kScreenW 320
#define kScreenH 616
const int a = 500;
const float b = 12.23;
内存分区与内存布局
在C语言中内存分为五个不同的区域,从高地址到低地址(内存地址编号从高到低)依次为栈区
,堆区
,静态全局区
,常量区
,代码区
栈区
- 主要存放自动变量,局部变量;
- 栈区内存的分配与回收由系统自动管理,无需开发人员手动管理.
堆区
- 通过C语言函数malloc,申请分配堆区内存,malloc函数返回一个指针;
- malloc函数申请的堆内存,回收释放调用free函数,堆内存是由开发人员手动管理的.
静态全局区
- 静态全局区由系统控制管理;
- 静态全局区的内存, 一旦分配, 就一直占用, 直到程序结束.
常量区
- 常量区由系统控制管理;
- 常量区的内容只能读不能修改;
代码区
- 代码区由系统控制管理,
- 代码区存放数据为: 程序中的函数编译后的指令.
//全局变量
int a = 100;
//静态全局变量
static int b = 200;
void MyFunction(int);
void MyFunction(int a)
{
printf(" a = %d",a);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//栈区
int c = 100;
NSLog(@" 栈区 -- 局部变量 = %p",&c);
int m = 666;
NSLog(@" 栈区 -- 局部变量 = %p",&m);
int k = 777;
NSLog(@" 栈区 -- 局部变量 = %p",&k);
//堆区
int *p = malloc(10);
NSLog(@" 堆区 = %p",p);
//全局/静态变量区
//静态局部变量
static int d = 500;
NSLog(@" 全局变量 = %p",&a);
NSLog(@" 静态全局变量 = %p",&b);
NSLog(@" 静态局部变量 = %p",&d);
//常量区
char *p1 = "iOS";
NSLog(@" 常量区 = %p",p1);
//代码区
NSLog(@" 代码区 = %p",MyFunction);
}
return 0;
}

内存布局中栈区与堆区的增长方向
- 栈区内存的增长方向从高地址 --> 低地址;
- 堆区内存的增长方向从低地址 --> 高地址.
int main(int argc, const char * argv[]) {
@autoreleasepool {
//栈区
int c = 100;
NSLog(@" 栈区 -- 局部变量 = %p",&c);
int m = 666;
NSLog(@" 栈区 -- 局部变量 = %p",&m);
int k = 777;
NSLog(@" 栈区 -- 局部变量 = %p",&k);
//堆区
int *p1 = malloc(10);
NSLog(@" 堆区 -- %p",p1);
int *p2 = malloc(10);
NSLog(@" 堆区 -- %p",p2);
int *p3 = malloc(10);
NSLog(@" 堆区 -- %p",p3);
}
return 0;
}

下图对最上面的知识点进行一个总结:

更深层次的理解堆栈内存
- 为什么栈向下增长?
这样设计可以使得堆和栈能够充分利用空闲的地址空间。如果栈向上涨的话,我们就必须得指定栈和堆的一个严格分界线,但这个分界线怎么确定呢?平均分?但是有的程序使用的堆空间比较多,而有的程序使用的栈空间比较多。所以就可能出现这种情况:一个程序因为栈溢出而崩溃的时候,其实它还有大量闲置的堆空间,但是我们却无法使用这些闲置的堆空间。所以最好的办法就是让堆和栈一个向上涨,一个向下涨,这样它们就可以最大程度地共用这块剩余的地址空间,达到利用率的最大化!!
- 为什么要把堆和栈分开?
- 从软件设计的角度来看,栈代表了处理逻辑,而堆代表了数据。
这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。- 堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的, 一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。
- 栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。
- 面向对象就是堆和栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美.
网友评论