#灵感#
当计算机执行一个程序时,必须有一种方法来存储程序本身和运算所得的数据。
总的来讲,计算机硬件中任何能够存储和检索信息的部分都是存储设备。当前运行的程序存放的存储器称为主存储器(primary storage),常常称 内存(memory)。自从1964年冯•诺依曼首次提出这个构思以来,计算机一直使用同一个存储器来存放组成程序的指令和计算过程中用到的数据。
人们将内存系统设计得十分高效,以使CPU能够以极快的速度获取它需要的信息。在如今的计算机中,内存通常构建在一个特殊的集成电路芯片上,这个集成电路芯片称作RAM,即随机访问存储器,它使程序能够在任何时刻访问任一存储单元。
~~~~~~~~~~----------------------··············分割线··············~~~~~~~~~~~~~~~~~~~~~~~~~~~
目录
以下内容是AI 助手总结的,可能不对,请谨慎辨别。
C语言中的内存管理是编程的重要部分,它涉及到程序运行时如何分配、使用和释放内存。以下是C语言内存管理的核心概念和相关内容:
1.内存区域划分
在C语言中,程序运行时的内存通常被划分为以下 4 个区域:
- 可执行代码区(Text Segment):
- 存储程序的机器代码。
- 这部分内存是只读的,防止程序意外修改自身的代码。
- 全局/静态数据区(Global/Static Data Segment):
- 存储全局变量和静态变量。
- 初始化的全局变量和静态变量存储在已初始化数据区。
- 未初始化的全局变量和静态变量存储在BSS段(Block Started by Symbol)
- 对于全局变量或使用static关键字修饰的变量,系统会在程序启动时为其分配内存,并且这些内存会一直保留到程序结束。
--------------可执行代码和静态数据,存储 在固定的内存位置。
- 堆区(Heap Segment):(程序可用的,未分配的内存资源称为堆)
- 动态分配的内存区域,由程序员通过malloc、calloc、realloc等函数申请。
- 使用完毕后需要通过free函数释放。
- 栈区(Stack Segment):
- 自动分配和释放的内存区域,用于存储函数调用时的局部变量、函数参数和返回地址。
- 栈的分配和释放由编译器自动完成。调用函数时给变量分配空间,函数返回时释放。
- 常量区(Constant Data Segment):
- 存储常量数据,如字符串字面量和const修饰的变量。
- 这部分内存通常是只读的。字符串常量存储在只读数据段或常量区,系统会自动分配内存并管理其生命周期。
经过上述讲解,需要程序员手动管理的只有堆区,而常量、全局变量、静态变量、字符串、函数的局部变量都是系统自动分配的。
2.动态内存分配
动态内存分配是指在程序运行时根据需要申请内存,主要通过以下函数实现:
- malloc(size_t size):
- 分配指定大小的内存块,返回指向该内存块起始位置的指针。
- 返回的内存未初始化。
- calloc(size_t num, size_t size):
- 分配num * size大小的内存块,并将其初始化为0。
- realloc(void *ptr, size_t size):
- 调整已分配内存块的大小,可以增大或缩小。
- free(void *ptr):
- 释放之前通过malloc、calloc或realloc分配的内存。
示例代码:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int*)malloc(5 * sizeof(int)); // 分配5个int大小的内存
if (p == NULL) {
printf("Memory allocation failed\n");
return 1;
}
for (int i = 0; i < 5; i++) {
p[i] = i + 1;
}
for (int i = 0; i < 5; i++) {
printf("%d ", p[i]);
}
printf("\n");
free(p); // 释放内存
return 0;
}
3.栈与堆的区别
特性 | 栈(Stack) | 堆(Heap) |
---|---|---|
分配方式 | 编译器自动分配和释放 | 程序员手动分配和释放 |
分配速度 | 快 | 较慢 |
生命周期 | 函数调用结束时自动销毁 | 需要手动释放 |
大小限制 | 较小(受系统限制) | 较大(受系统内存限制) |
4.常见问题
- 内存泄漏:
- 动态分配的内存未被释放,导致程序占用的内存不断增加。
- 解决方法:确保每次malloc或calloc都有对应的free。
- 野指针:
- 指向已释放内存或未分配内存的指针。
- 解决方法:将释放后的指针置为NULL。
- 越界访问:
- 访问数组或指针超出其分配范围。
- 解决方法:确保访问索引在合法范围内。
引申
malloc
例句:p_child = (struct diag_child *)malloc(sizeof(struct diag_child));
malloc函数返回类型为void * 指针值,待分配的内存以字节为单位。原型为:
void *malloc(int nBytes); // * 表示结果是和函数名而非基本类型相联系的指针。
ANSI C能在指向void的指针类型和指向基本类型的指针类型间自动进行转换。例如,如果声明字符指针cp为:
char *cp;
就可以用语句
cp = malloc(10);// 1 10个字节空间
将malloc的结果直接赋给cp。许多程序员在赋值之前,会使用下列强制类型转换将malloc返回的结果转换成指向字符的指针。这样做的一部分原因是因为历史因素,另一部分原因是这样做能使指针类型间的转换更清楚。
cp = (char *) malloc (10) ; // 2
不管有没有明确使用强制类型转换,这条语句均可分配10个字节的新内存空间,并将第一个字节的地址存放在cp中。
实例:
调用malloc时要检查
由于计算机内存系统的大小是有限的,堆的空间终会用完。此时malloc返回指针NULL表示分配所需内存块的工作失败。谨慎的程序员应该在每次调用malloc时都检查失败的可能性。所以,分配一个动态数组后,需要写如下语句:
arr = malloc(10 * sizeof (int)) ;
if (arr == NULL) Error ("No memory available");
因为动态分配往往会频繁用于多个程序,所以检查错误是必要的。(堆内存的大小取决于操作系统、硬件限制以及程序的具体实现)
使用完后释放内存
例句:
free(arr);
arr = NULL;//将p置为NULL,防止越界访问
保证不会发生内存不够的一种方法是一旦使用完已分配的空间就立刻释放它。标准ANSI库提供了函数free,用于归还以前由malloc分配出去的堆内存。例如,如果你能肯定已经不再使用分配给arr的内存,就可以通过调用
free(arr);
释放该空间。
/*但事实证明,知道何时释放一块内存并不那么容易。根本问题在于分配和释放内存的操作分别属于接口两边的实现及客户。实现知道何时该分配内存,返回指针给客户,但它并不知道何时客户结束使用已分配的对象,所以释放内存是客户的责任,虽然客户可能并不十分了解对象的结构。
对现在的大多数计算机的内存来说,你可以随意分配所需内存而不需要考虑释放内存的问题。这种策略几乎对所有运行时间不长的程序还是有效的。内存有限的问题只有在设计一个需要运行很长时间的应用,比如所有其他系统所依靠的操作系统时,才变得有意义。在这些应用中,不需要内存时释放它们是很重要的。
某些语言支持那些能主动检查正在使用的内存,并释放不再使用的内存的动态分配系统。此策略称为碎片收集(garbage collection)。C语言中也有碎片收集分配器,而且它未来的应用也许会更广泛。如果是这样的话,即使在长时间运行的程序中,也可以忽略释放内存的问题,因为你可以依靠碎片收集器自动执行释放内存的操作。*/
动态数组
例句:
int *arr;
arr = malloc(10 * sizeof (int));
分配在堆上并用指针变量引用的数组称为动态数组(dynamic array),它在现代程序设计中有非常重要的作用。一般来说,分配一个动态数组包含以下步骤:
1)声明一个指针变量,用以保存数组基地址。
2)调用malloc函数为数组中的元素分配内存。由于不同的数据类型要求不同大小的内存空间,所以ma11oc调用必须分配的字节大小= 数组元素数* 每个元素字节大小 的内存空间。
3) 将malloc的结果赋给指针变量。
比如,要给一个含10个元素的整型数组分配空间,然后将该内存赋给变量arr,你必须先用
int *arr;
声明arr,随后使用
arr = malloc(10 * sizeof (int));
来分配空间。
memset
例句: memset(p_child, 0, sizeof(*p_child));
1、memset的作用是将某一块内存中的内容 全部设置为指定的值。
void *dest为内存的地址,int c是赋予的值,size_t count是需要赋值的字节长度;
#include <memory.h>
#include <stdio.h>
void main(){
char string1[]="hello,hello";
printf("1 is %s\n",string1);
memset(&string1,'*',6);
printf("2 is %s\n",string1);
}
输出结果:
2、memset 通常用于为malloc新申请的内存做初始化工作。(作用的对象是一块内存)
#include <memory.h>
#include <stdio.h>
typedef struct diag_child {
char name[40];
}child;
void main(){
struct diag_child *p_child;
p_child = (struct diag_child *)malloc(sizeof(struct diag_child));
printf("size is %d \n",sizeof(struct diag_child));
if (p_child == NULL) {
printf(" malloc fail!\n");
return NULL;
}
memset(p_child, 0, sizeof(*p_child));
for(int i=0;i<40;i++) printf("%d",p_child->name[i]);
printf("\nchar is %d \n",p_child->name[6]);
}
结果:
char **p
定义一个指针变量p, 指向另外一个指针变量,该指针变量又指向一个基本字符型变量。指针运算符是从右向左的,所以char **p, 等价于 char* (*p);
char* 是一个指向字符(char)的指针,通常用于表示字符串。
char** 则是一个指向 char* 的指针,这意味着它可以指向一个 char* 类型的变量。这种类型的指针通常用于处理字符串数组或动态分配的字符串列表。
例如:
char *str1 = "Hello";
char *str2 = "World";
char **p;
p = &str1; // p 现在指向 str1
printf("%s\n", *p); // 输出: Hello
p = &str2; // p 现在指向 str2
printf("%s\n", *p); // 输出: World
例子中,p 被用来存储 str1 和 str2 的地址,这两个变量本身是字符串(字符数组)的指针。