文章时效性提示
本文发布于 504 天前,部分信息可能已经改变,请注意甄别。
为什么存在动态内存分配
- 之前开辟空间大小是固定的
- 数组在创建时必须制定数组长度。
动态内存分配,解决上面的问题。可以让内存分配更灵活。
动态内存函数介绍
malloc
动态内存开辟。需要引用stdlib.h头文件。
void* malloc(size_t size);
malloc会返回一个指针指向开辟好的空间,当申请空间失败时,会返回一个NULL。
注意⚠️:使用这个函数,一定要检查是否申请成功,因为它可能申请失败。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { int* p = (int*)malloc(10*sizeof(int)); if (p==NULL) { printf("%s\n",strerror(errno)); } else { int i = 0; for (i=0; i<10; i++) { *( p + i ) = i; } for (i=0; i<10; i++) { printf("%d ",*(p+i)); } } return 0; }
|
打印的结果是0 1 2 3 4 5 6 7 8 9
把申请10个int型的空间修改为int* p = (int*)malloc(INTMAX_MAX);时,返回Cannot allocate memory没有足够的空间。
如果申请的size空间为0,这种行为是标准未定义的,取决于编译器。(不要这样写)
free
对动态开辟的空间进行回收和释放。
void free(void *memblock);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { int* p = (int*)malloc(40); if (p==NULL) { printf("%s\n",strerror(errno)); }else { int i = 0; for (i=0; i<10; i++) { *( p + i ) = i; } for (i=0; i<10; i++) { printf("%d ",*(p+i)); } } free (p); p = NULL; return 0; }
|
free (p);将空间释放。但是p的值是没有改变的。
使用free释放空间后最好运行:p = NULL;
free的空间必须是动态开辟出来的,如果不是动态开辟出来的,这种行为是标准未定义的。
当程序结束时,即使没写free函数,操作系统也会将空间回收♻️。
calloc
开辟空间,并把元素改成0。
void *calloc(size_t num,size_t size);
size_t num是元素个数,size_t size是每个元素的大小(字节)。
开辟空间,用指针返回,空间不足时,返回空指针NULL。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <stdio.h> #include <libc.h> #include <errno.h> int main() { int* p = calloc(10, sizeof(int)); if (p==NULL) { printf("%s\n",strerror(errno)); } else { int i = 0; for (i = 0; i<10; i++) { printf("%d ",*(p+i)); } } free(p); p = NULL; return 0; }
|
打印的结果是0 0 0 0 0 0 0 0 0 0
calloc和malloc的区别
希望开辟的空间内容为0,使用calloc;
希望开辟的空间不要初始化,使用malloc;
realloc
realloc让动态开辟的空间更灵活。
可以调整动态开辟空间的内存大小。
void *realloc(void *memblock,size_t size);
void *memblock是之前开辟好的动态空间,size_t size是新的大小(字节)。
返回一个指针指向新开辟的内存块,
realloc使用注意事项:
- 如果p指向的空间之后有足够的内存空间,直接追加内存,后返回p
- 如果p指向的空间之后没有足够的内存空间,则realloc会重新找一块新的内存区域,开辟一块新的满足需求的空间,并把原先内存中的拷贝至新内存空间,释放旧内存空间,最后返回新开辟的内存空间地址。
- 使用新变量来接受realloc的返回值,因为它可能申请失败,使用新变量来避免之前保存的内容丢失。
- 当动态申请的空间不使用的时候,要还给操作系统。使用free函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { int *p = (int*)malloc(20); if ( p == NULL ) { printf("%s\n",strerror(errno)); } else { int i = 0; for (i=0; i<5; i++) { *(p+i) = i; } } int* p2 = realloc(p, 40); if (p2 == NULL) { printf("%s\n",strerror(errno)); } else { p = p2; int i = 0; for (i=5; i<10; i++) { *(p+i) = i; } for (i=0; i<10; i++) { printf("%d ",*(p+i)); } } free(p); p = NULL; return 0; }
|
常见的动态内存错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int *p = (int*)malloc(40); int i = 0; for (i=0; i<10; i++) { *(p+i) = i; } free(p); p = NULL; return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int *p = (int*)malloc(5 * sizeof(int)); if (p==NULL) { return 0; } else { int i = 0; for (i=0; i<10; i++) { *(p+i) = i; } } free(p); p = NULL; return 0; }
|
1 2 3 4 5 6 7 8 9 10
| #include <stdio.h> int main() { int a = 10; int* p = &a; *p = 20; free(p); p = NULL; return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int* p = (int*)malloc(40); if (p == NULL) { return 0; } int i = 0; for (i=0; i<10; i++) { *p++ = i; } free(p); p = NULL; return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int* p = (int*)malloc(40); if (p==NULL) { return 0; } free(p); free(p); return 0; }
|
为避免这种情况,谁开辟,谁回收。
但只要第一是释放之后,把p置成了空指针,后面的释放就没有意义了,程序可以正常运行。
1 2 3 4 5 6 7 8 9 10 11 12
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main() { while (1) { malloc(1); } return 0; }
|
C/C++程序的内存开辟
C/C++程序内存分配有4个区域
- 栈区(stack):在执行函数时,函数内局部变量的存储单元在栈上创建,函数结束时,被释放。栈区主要存放运行函数分配的局部变量、函数参数、返回数据、返回地址等。
- 堆区(heap):一般由程序员分配释放,程序员不释放会由OS回收。分配方式类似链表
- 数据段(静态区)(static):存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体的二进制代码
普通的局部变量是在栈区分配空间的,栈区的特点就是创建的变量出了作用域就销毁了。
被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直至程序结束才销毁。
柔性数组
结构中最后一个元素允许是未知大小的数组,这就是柔性数组成员
1 2 3 4 5 6 7 8 9 10 11
| #include <stdio.h> struct S { int n; int arr[]; }; int main() { struct S s; }
|
上面结构体S中的整型arr数组是未知大小的,就是柔性数组成员,柔性的意思就是:数组大小是可以调整的。
柔性数组的大小
1 2 3 4 5 6 7 8 9 10
| struct S { int n; int arr[]; }; int main() { struct S s; printf("%d\n",sizeof(s)); }
|
打印的结果是4。
包含柔性数组成员的结构体计算大小时,不包含柔性数组成员的大小。
柔性数组的使用
柔性数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| struct S//先创建一个包含柔性数组成员的结构体 { int n; int arr[]; }; int main() { struct S* ps = (struct S*)malloc(sizeof(struct S)+5*sizeof(int)) ; ps->n = 100; int i = 0; for (i=0; i<5; i++) { ps->arr[i] = i; } struct S* ptr = realloc(ps, 44); if (ptr != NULL) { ps = ptr; } for (i=5; i<10; i++) { ps->arr[i] = i; } for (i=0; i<10; i++) { printf("%d",ps->arr[i]); } free(ps); ps = NULL; return 0; }
|
使用指针维护的结构体数组数组(和柔性数组实现的功能一致):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| struct S { int n; int *arr; }; int main() { struct S *ps = (struct S*)malloc(sizeof(struct S)); ps->arr = malloc(5*sizeof(int)); int i = 0; for (i=0; i<5; i++) { ps->arr[i] = i; } for (i=0; i<5; i++) { printf("%d ",ps->arr[i]); } int *ptr = realloc(ps->arr, 10*sizeof(int)); if (ptr != NULL) { ps->arr = ptr; } for (i=5; i<10; i++) { ps->arr[i] = i; } for (i=0; i<10; i++) { printf("%d ",ps->arr[i]); } free(ps->arr); ps->arr = NULL; free(ps); ps = NULL; return 0; }
|
柔性数组的特点
- 结构中的柔性数组成员前面必须至少有一个其他成员
- sizeof返回的这种结构大小不包括柔性数组的内存
- 包含柔性数组成员的结构用
malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
优点: