内存分区
- 代码区
- 全局区/静态区
- 堆区
- 栈区
划分
程序运行前:代码区、全局区/静态区
程序运行后:栈区、堆区
代码区
作用
存放CPU执行的二进制指令
特点
- 只读
- 共享(执行的代码都在一块区域)
栈区
特点
- 先进后出,编译器自动分配释放数据
- 主要存放函数的形参,局部变量
- 函数运行结束自动释放对应变量
- 空间较小,不适合存放大量数据
注意事项
- 不要返回局部变量的地址
int *fun(int a, int b) {
int c = a + b;
return &c;
}
int main(void) {
int *p = fun(3, 5);
//局部变量函数执行后已释放,再次访问属于非法操作内存
printf ("%d\n", *p);
return 0;
}
堆区
特点
- 开发人员手动申请和释放,释放前,该块堆区间可一直使用,若开发人员不释放,程序结束后由系统回收内存
- 堆空间一般没有软限制,只受限于硬件,适宜存放较大数据
堆区使用
int* func(int a, int b) {
int *c = (int *)malloc(sizeof(int) * 2);
if (c == NULL) {
printf("malloc failed!\n");
return NULL;
}
c[0] = a;
c[1] = b;
return c;
}
int main(void) {
int *p = func(1, 3);
return 0;
}
callow和realloc
内存申请可使用三个函数来完成,分别为malloc、callow、realloc,内存释放只需使用free函数。
- malloc函数
原型:*void malloc(unsigned int num_bytes)
用法:分配长度为num_bytes字节的内存块
说明:如果分配成功则返回指向被分配内存的指针,否则返回NULL - calloc函数
原型:*void callow(int num_elems, int elem_size)
用法:为具有num_elems个长度为elem_size元素的数组分配内存(自动初始化为0)
说明:如果分配成功则返回指向被分配内存的指针,否则返回NULL - realloc函数
原型:*void *realloc(void mem_address, unsigned int newsize)
用法:改变mem_address所指内存区域大小为newsize长度(会保留/拷贝原有数据)
说明:如果分配成功则返回指向被分配内存的指针,否则返回NULL - free函数
原型:*void free(void p)
用法:释放指针p所指向的内存空间
说明:p所指向的内存空间必须是用calloc, realloc, malloc所分配的内存,如果p为NULL则不做任何操作
全局/静态区
特点
- 存储全局变量、静态变量、常量,该区变量在程序运行期间一直存在
- 程序结束由系统回收
- 已初始化的数据放在data段,未初始化的数据放到bss段
- 该区变量未初始化时,默认被初始化为0
int global_i; //全局变量, 默认初始化为0
static int st_i = 10; //定义时需要被初始化且只会初始化一次(若没有初始化,默认初始化为0),只能在当前文件使用,无法与extern修饰一起
char *func() {
return "hello";
}
常量
- const修饰的变量
- 修饰全局的变量, 受到常量区的保护,一旦修改程序崩溃
const int a = 10;
void test() {
c_a = 20; //直接修改 失败
int *p = &a;
*p = 20; //间接修改 失败
}
2. 修饰局部变量 放在栈区,可以间接修改
void test() {
const int b = 20;
b = 10; //直接修改 失败
int *p = &b;
*p = 10; //间接修改 成功
int arr[b]; //错误 伪常量不可以初始化数组
}
- 字符串常量
- ANSI C中规定:修改字符串常量,结果是未定义的
- 有些编译器把多个相同字符串常量看成一个,有些则不进行此优化
- 部分编译器可以修改字符串常量,部分编译器不可以
void test() {
char *p1 = "hello world";
char *p2 = "hello world";
char *p3 = "hello world";
printf("%d, %d, %d\n", p1, p2, p3); //结果相同, vs下相同字符串常量共享
p[0] = 'x'; //错误 vs中字符串常量无法修改
}
函数调用模型
宏函数
概念
- 宏函数和宏常量都是利用*#define*定义出来的内容
- 项目中经常把一些短小而又频繁使用的函数写成宏函数
- 宏函数没有普通函数参数压栈、跳转、返回等时间上的开销,可以提高程序的效率
#define ADD(x, y) x+y
void test() {
int a = 10;
int b = 20;
printf("a + b = %d\n", ADD(a, b)*20); //结果是10 + 20 * 20, 预处理时会展开ADD(a, b)*20为 a + b*20
注意
通常需要加括号,以保证运算的完整
函数调用流程
调用惯例
概念
函数的调用方和被调用方对于函数是如何调用的必须有一个明确的约定, 只有双方都遵循同样的约定,函数才能被正确的调用,这样的约定被称为调用惯例
c/c++中存在多个调用惯例,默认使用的调用惯例为cdecl
调用惯例包含的内容有:
调用惯例 出栈方 参数传递 名字修饰
cdecl 函数调用方 从右至左参数入栈 下划线+参数名
栈的生长方向
栈底高地址,栈顶低地址
内存存储方式
大端对齐模式:低位字节存储在高地址端, 高位字节存储在低地址端
小端对齐模式:低对低,高对高
1298

被折叠的 条评论
为什么被折叠?



