【C】内存布局
动态内存管理
动态内存管理的几个函数
malloc
-
函数原型:
void *malloc(size_t size);
-
malloc函数向系统申请分配size个字节的内存空间,并返回一个指向这块空间的指针(void类型)。 -
如果函数调用成功,返回一个指向申请的内存空间的指针,由于返回类型是
void指针(void *),所以它可以被转换成任何类型的数据;如果函数调用失败,返回值是NULL。另外,如果size参数设置为0, 返回值也可能是NULL,但这并不意味着函数调用失败。#include<stdio.h> #include<stdlib.h> int main() { int *ptr; ptr = (int *)malloc(sizeof(int)); //因为不是所有的操作系统的int都是4个字节,所以参数使用sizeof() // (int *)将返回的void指针转化为int指针 if (ptr == NULL) { printf("分配内存失败了! \n"); exit(1); //退出程序,需导入头文件<stdlib.h> } printf("请输入一个整数 : "); scanf("%d", ptr); printf("你输入的整数是 : %d\n", *ptr); return 0; }运行结果
请输入一个整数 : 100 你输入的整数是 : 100 -
注意:由于
malloc申请的空间位于内存的堆上,如果你不主动释放堆上的内存资源,那么它将永远存在,直到程序被关闭。所以当我们不再使用这块内存时,要自己动手释放,避免造成内存泄漏。 -
malloc不仅可以申请存储基本数据类型的空间,事实上它还可以申请一块任意尺寸的内存空间。对于后者,由于申请得到的空间是连续的,所以我们经常用数组来进行索引即可:#include <stdio.h> #include <stdlib.h> int main(void) { int *ptr = NULL; int num, i; printf("请输入待录入整数的个数:"); scanf("%d", &num); ptr = (int *)malloc(num * sizeof(int)); for (i = 0; i < num; i++) { printf("请录入第%d个整数:", i+1); scanf("%d", &ptr[i]); } printf("你录入的整数是:"); for (i = 0; i < num; i++) { printf("%d ", ptr[i]); } putchar('\n'); free(ptr); return 0; }
free
-
函数原型:
void free(void *ptr);
-
free函数释放ptr参数指向的内存空间。该内存空间必须是由malloc、calloc或realloc函数申请的。否则,该函数将导致未定义行为。如果ptr
参数是NULL,不执行任何操作。注意:该函数并不会修改
ptr参数的值,所以调用后它仍然指向原来的地方(变为非法空间)。#include<stdio.h> #include<stdlib.h> int main() { int *ptr; ptr = (int *)malloc(sizeof(int)); //因为不是所有的操作系统的int都是4个字节,所以参数使用sizeof() // (int *)将返回的void指针转化为int指针 if (ptr == NULL) { printf("分配内存失败了! \n"); exit(1); //退出程序,需导入头文件<stdlib.h> } printf("请输入一个整数 : "); scanf("%d", ptr); printf("你输入的整数是 : %d\n", *ptr); free(ptr); //释放内存空间 printf("你输入的整数是 : %d\n", *ptr); //再次打印会出错 return 0; }
内存泄漏
导致内存泄漏主要有两种情况:
-
隐式内存泄漏(即用完内存块没有及时使用free函数释放)
注: 下面这段程序可能会造成系统假死,运行需谨慎
#include<stdio.h> #include<stdlib.h> int main() { while (1) // 死循环 { malloc(1024); //不断地申请内存空间 } return 0; } -
丢失内存块地址
#include<stdio.h> #include<stdlib.h> int main() { int *ptr; int num = 123; ptr = (int *)malloc(sizeof(int)); //因为不是所有的操作系统的int都是4个字节,所以参数使用sizeof() // (int *)将返回的void指针转化为int指针 if (ptr == NULL) { printf("分配内存失败了! \n"); exit(1); //退出程序,需导入头文件<stdlib.h> } printf("请输入一个整数 : "); scanf("%d", ptr); printf("你输入的整数是 : %d\n", *ptr); ptr = # //ptr指向局部变量 printf("你输入的整数是 : %d\n", *ptr); free(ptr); //此时ptr指向的不是malloc申请的内存空间,使用free函数释放会出错 return 0; }
初始化内存空间
由于malloc并不会帮你初始化申请的内存空间,所以需要自己进行初始化。
memset
-
以men开头的函数被编入字符串标准库,函数的声明包含在
string.h这个头文件中:memset—— 使用一个常量字节填充内存空间memcpy—— 拷贝内存空间memmove—— 拷贝内存空间memcmp—— 比较内存空间memchr—— 在内存空间中搜索一个字符
-
memset函数可以用来初始化内存空间#include <stdio.h> #include <stdlib.h> #include <string.h> #define N 10 int main(void) { int *ptr = NULL; int i; ptr = (int *)malloc(N*sizeof(int)); if (ptr == NULL) { exit(1); } memset(ptr, 0, N*sizeof(int)); for (i = 0;i < N; i++) { printf("%d ",ptr[i]); } putchar('\n'); free(ptr); }
calloc
-
函数原型:
void *calloc(size_t nmemb, size_t size);
-
calloc函数在内存中动态地申请nmemb个长度为size的连续内存空间(即申请的总空间尺寸为nmemb * size),这些内存空间全部被初始化为0. -
calloc函数与malloc函数的一个重要区别是:calloc函数在申请完内存后,自动初始化该内存空间为零malloc函数不进行初始化操作,里边数据是随机的
-
下面两种写法是等价的:
// calloc() 分配内存空间并初始化 int *ptr = (int *)calloc(8, sizeof(int)); //两个参数// malloc() 分配内存空间并用 memset() 初始化 int *ptr = (int *)malloc(8 * sizeof(int)); //一个参数 memset(ptr, 0, 8 * sizeof(int));
拓展内存空间
memcpy
有时候我们可能会需要对原来的分配的内存空间进行拓展,这时就需要再调用一次malloc 申请一个更大的内存空间,再使用memcpy将原来的数据搬进去
-
函数原型:
void *memcpy(void *dest,const void *src, size_t n);
-
memcpy函数从 src 指向的内存空间拷贝 n 个字节到dest指向的内存空间。src和dest指向的内存区域不能出现重叠,否则应该使用memmove函数。 -
memcpy函数并不关心被复制的数据类型,只是逐字节地进行复制,这给函数的使用带来了很大的灵活性,可以面向任何数据类型进行复制。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
int *ptr1 = NULL;
int *ptr2 = NULL;
//第一次申请的内存空间
ptr1 = (int *)malloc(10 * sizeof(int));
//进行若干操作之后发现ptr1申请的内存空间不够用
//第二次申请的内存空间
ptr2 = (int *)malloc(20 * sizeof(int));
//将ptr1的数据拷贝到ptr2中
memcpy(ptr2, ptr1, 10);
free(ptr1);
//对ptr2申请的内存空间进行若干操作...
free(ptr2); //释放对ptr2申请的内存空间
return 0;
}
realloc
-
函数原型:
void *realloc(void *ptr, size_t size);
-
以下几点是需要注意的:
realloc函数修改ptr指向的内存空间大小为size字节- 如果新分配的内存空间比原来的大,则旧内存块的数据不会发生改变;如果新的内存空间的大小小于旧的内存空间,可能会导致数据丢失,慎用!
- 该函数将移动内存空间的数据并返回新的指针
- 如果ptr参数为NULL,那么调用该函数就相当于调用
malloc(size) - 如果size参数为 0,并且ptr参数不为NULL,那么调用该函数就相当于调用
free(ptr) - 除非ptr参数为NULL,否则ptr的值必须由先前调用
malloc、calloc或realloc函数返回
-
设计一个程序,要求:
- 不使用
malloc函数,使用realloc函数申请内存空间 - 让用户一直输入一个整数,用户每输入一个整数就新增加一个整形空间存放。直到用户输入-1表示输入结束,再把输入的所有整数打印出来
#include<stdio.h> #include<stdlib.h> int main(void) { int i, num; int count = 0; int *ptr = NULL; //注意,这里必须初始化为NULL do { printf("请输入一个整数(输入-1表示结束) : "); scanf("%d",&num); count++; ptr = (int *)realloc(ptr, count * sizeof(int)); if (ptr == NULL) { exit(1); } ptr[count-1] = num; }while (num != -1); printf("输入的整数分别是 : "); for (i=0;i<count;i++) { printf("%d ",ptr[i]); } putchar('\n'); free(ptr); return 0; }运行结果
请输入一个整数(输入-1表示结束) : 1 请输入一个整数(输入-1表示结束) : 2 请输入一个整数(输入-1表示结束) : 3 请输入一个整数(输入-1表示结束) : -1 输入的整数分别是 : 1 2 3 -1 //如果不想打印-1,把for循环条件表达式改为"i<count-1"即可 - 不使用
C语言的内存布局
#include<stdio.h>
#include<stdlib.h>
int global_uninit_var;
int global_init_var1 = 520;
int global_init_var2 = 880;
void func(void);
void func(void)
{
;
}
int main(void)
{
int local_var1;
int local_var2;
static int static_uninit_var;
static int static_init_var = 456;
char *str1 = "I love FishC.com!";
char *str2 = "You are right!";
int *malloc_var = (int *)malloc(sizeof(int));
printf("addr of func -> %p\n",func);
printf("addr of str1 -> %p\n",str1);
printf("addr of str2 -> %p\n",str2);
printf("addr of global_init_var1 -> %p\n",&global_init_var1);
printf("addr of global_init_var2 -> %p\n",&global_init_var2);
printf("addr of static_init_var -> %p\n",&static_init_var);
printf("addr of static_uninit_var -> %p\n",&static_uninit_var);
printf("addr of global_uninit_var -> %p\n",&global_uninit_var);
printf("addr of malloc_var -> %p\n",malloc_var);
printf("addr of local_var1 -> %p\n",&local_var1);
printf("addr of local_var2 -> %p\n",&local_var2);
return 0;
}
运行结果:
addr of func -> 00007ff754751591
addr of str1 -> 00007ff754759000
addr of str2 -> 00007ff754759012
addr of global_init_var1 -> 00007ff754758010
addr of global_init_var2 -> 00007ff754758014
addr of static_init_var -> 00007ff754758018
addr of static_uninit_var -> 00007ff75475c044
addr of global_uninit_var -> 00007ff75475c040
addr of malloc_var -> 0000021f4af616a0
addr of local_var1 -> 000000d8845ff674
addr of local_var2 -> 000000d8845ff670
//注意: 每个人的结果不一定相同
规律:

C语言内存空间划分

根据内存地址从低到高分别划分为:
- 代码段(Text segment)
- 数据段(Initialized data segment)
- BSS段(Bss segment/Uninitialized data segment)
- 栈(Stack)
- 堆(Heap)
代码段
- 代码段通常是指用来存放程序执行代码的一块内存区域。
- 这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读。
- 在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
数据段
- 数据段通常用来存放已经初始化的全局变量和局部静态变量。
BSS段
- BSS 段通常是指用来存放程序中未初始化的全局变量的一块内存区域。
- BSS 是英文 Block Started by Symbol 的简称,这个区段中的数据在程序运行前将被自动初始化为数字 0。
堆
- 前边我们学习了动态内存管理函数,使用它们申请的内存空间就是分配在这个堆里边。
- 所以,堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩展或缩小。
- 当进程调用
malloc等函数分配内存时,新分配的内存就被动态添加到堆上;当利用free等函数释放内存时,被释放的内存从堆中被剔除。
栈
- 栈是函数执行的内存区域,通常和堆共享同一片区域。
栈和堆的区别
堆和栈则是 C 语言运行时最重要的元素,下面我们将两者进行对比。
-
申请方式:
- 堆由程序员手动申请
- 栈由系统自动分配
-
释放方式:
- 堆由程序员手动释放
- 栈由系统自动释放
-
生存周期:
-
堆的生存周期由动态申请到程序员主动释放为止,不同函数之间均可自由访问
-
栈的生存周期由函数调用开始到函数返回时结束,函数之间的局部变量不能互相访问
#include<stdio.h> #include<stdlib.h> int *func(void) { int *ptr = NULL; ptr = (int *)malloc(sizeof(int)); //在堆上申请一个内存空间 if (ptr == NULL) { exit(1); } *ptr = 520; return ptr; //返回ptr的地址 } int main(void) { int *ptr = NULL; ptr = func(); printf("%d\n",*ptr); //在main函数中访问func函数中申请的内存空间的值 free(ptr); }注意:这段代码中,在
func函数中申请内存空间,在main函数中访问并释放。但实战中不建议这样做,建议在哪个函数中申请了内存空间就在哪个函数中释放。
-
-
发展方向:
-
堆和其它区段一样,都是从低地址向高地址发展
-
栈则相反,是由高地址向低地址发展
#include<stdio.h> #include<stdlib.h> int main(void) { int *ptr1 = NULL; //指针变量存放在栈中 int *ptr2 = NULL; ptr1 = (int *)malloc(sizeof(int)); //内存空间存放在堆中 ptr2 = (int *)malloc(sizeof(int)); printf("stack: %p -> %p\n",&ptr1, &ptr2); //栈 -> 指针变量的地址 printf("heap: %p -> %p\n",ptr1,ptr2); //堆 -> 指针变量指向的地址 return 0; }运行结果
stack: 00000060fadffb28 -> 00000060fadffb20 //栈 高 -> 低 heap: 000001a4c5fe16a0 -> 000001a4c5fe16e0 //堆 低 -> 高
-

浙公网安备 33010602011771号