C语言动态内存管理

```c
ptr = (int*)malloc(num*sizeof(int));

malloc 函数:
malloc 是C标准库中的一个函数,用于动态分配内存。它的名字来源于 “memory allocation”。当你使用 malloc,你告诉它你想要多少字节的内存,然后它会找到一个足够大的内存块并将其返回给你。这个内存块是连续的。
num * sizeof(int):
这部分代码计算了你需要的内存的总字节数。num 是你想要分配的整数的数量,而 sizeof(int) 告诉你一个整数在内存中占据多少字节。在大多数现代系统上,sizeof(int) 通常是4字节,但这取决于系统和编译器。
(int*)
这是一个类型转换,也称为强制类型转换或强制转换。malloc 返回的是一个 void* 类型的指针,这意味着它返回的是一个通用的、可以指向任何类型的指针。但是,在C语言中,你不能直接对 void* 指针进行解引用。因此,你需要将它转换为你想要的具体类型。在这里,(int*) 将 void* 指针转换为 int* 指针,这意味着你现在可以用这个指针来访问整数。
ptr:
这是一个 int* 类型的指针变量,用于存储 malloc 返回的内存地址。一旦你有了这个地址,你就可以使用它来读写内存中的整数。
总的来说,这段代码的目的是动态分配足够的内存来存储 num 个整数,并将分配的内存的起始地址存储在 ptr 指针中。之后,你可以使用 ptr 来访问这块内存。但请记住,当你不再需要这块内存时,应使用 free(ptr); 来释放它,以防止内存泄漏。

int *p = (int*)calloc(10, sizeof(int));

calloc 函数:
calloc 是C标准库中的一个函数,用于动态分配内存。它的名字来源于 “contiguous allocation”。与 malloc 类似,calloc 也用于动态分配内存,但它有一个额外的特性:它会自动将分配的内存初始化为0。
10 * sizeof(int):
这部分代码计算了你需要的内存的总字节数。10 是你想要分配的整数的数量,而 sizeof(int) 告诉你一个整数在内存中占据多少字节。在大多数现代系统上,sizeof(int) 通常是4字节,但这取决于系统和编译器。
(int*):
这是一个类型转换,也称为强制类型转换或强制转换。calloc 返回的是一个 void* 类型的指针,这意味着它返回的是一个通用的、可以指向任何类型的指针。但是,在C语言中,你不能直接对 void* 指针进行解引用。因此,你需要将它转换为你想要的具体类型。在这里,(int*) 将 void* 指针转换为 int* 指针,这意味着你现在可以用这个指针来访问整数。
p:
这是一个 int* 类型的指针变量,用于存储 calloc 返回的内存地址。一旦你有了这个地址,你就可以使用它来读写内存中的整数。由于使用了 calloc,这块内存已经被初始化为0。
总的来说,这段代码的目的是动态分配足够的内存来存储10个整数,并将分配的内存的起始地址存储在 p 指针中。这块内存已经被初始化为0。但请记住,当你不再需要这块内存时,应使用 free§; 来释放它,以防止内存泄漏。

C语言内存管理涉及以下几个主要方面:

内存分区:在C语言中,内存主要分为四个区域:堆区(heap)、栈区(stack)、全局/静态存储区(global/static storage)和代码区(text segment)。每个区域有其特定的用途和特性。

堆区:由malloc、calloc和realloc等函数分配的内存位于堆区。这块内存的大小在运行时动态分配,并由程序员负责释放。如果程序员不释放这块内存,就会导致内存泄漏。
栈区:函数调用时使用的局部变量和参数存储在栈区。栈区的内存分配和释放由编译器自动管理。当函数返回时,其使用的栈内存会被自动释放。
全局/静态存储区:全局变量和静态变量(包括静态局部变量和静态全局变量)存储在全局/静态存储区。这块内存在程序的生命周期内一直存在。
代码区:也称为文本段,存储程序的二进制代码。这块内存是只读的,以防止程序在运行时修改其指令。
内存分配与释放:C语言提供了几个用于内存分配和释放的函数。

malloc(size_t size):分配指定字节数的未初始化内存,并返回指向这块内存的指针。如果分配失败,返回NULL。
calloc(size_t num, size_t size):分配足够的内存来存储num个大小为size的对象,并将内存初始化为0。返回指向这块内存的指针,或在失败时返回NULL。
realloc(void *ptr, size_t size):尝试调整之前由malloc或calloc分配的内存块的大小。如果成功,返回一个指向新内存块的指针;否则,返回NULL。
free(void *ptr):释放之前由malloc、calloc或realloc分配的内存。
指针与内存地址:在C语言中,指针是一个变量,用于存储另一个变量的内存地址。通过指针,可以间接地访问和操作内存中的数据。

常见内存问题:

内存泄漏:当使用malloc、calloc或realloc分配的内存没有被正确地释放时,就会发生内存泄漏。这会导致程序随着时间的推移消耗越来越多的内存。
悬挂指针:当指针指向的内存已经被释放,但指针本身没有被设置为NULL时,就会出现悬挂指针。试图通过悬挂指针访问内存会导致未定义行为。
越界访问:数组和指针的越界访问可能导致程序读取或写入不应该访问的内存区域,从而引发未定义行为或安全问题。
野指针:未初始化的指针称为野指针。试图通过野指针访问内存会导致未定义行为。
内存对齐:为了提高访问效率,计算机硬件通常要求数据的内存地址以特定的倍数对齐。在C语言中,可以使用alignas关键字或#pragma pack指令来控制特定类型或结构的内存对齐方式。

垃圾回收:C语言没有内置的垃圾回收机制,所以程序员必须手动管理内存。这意味着你需要负责释放不再需要的内存,以防止内存泄漏。

动态内存管理的优缺点:

优点:动态内存管理允许程序在运行时根据需要分配和释放内存,这使得程序更加灵活和高效。
缺点:动态内存管理需要程序员仔细处理内存分配和释放,否则容易导致内存泄漏、悬挂指针等问题。此外,动态内存分配和释放通常比静态内存分配更加耗时。
掌握C语言的内存管理知识对于编写高效、健壮的程序至关重要。

例子1

void GetMemory(char *p)
{
 p = (char *)malloc(100);
}
void Test(void)
{
 char *str = NULL;
 GetMemory(str);
 strcpy(str, "hello world");
 printf(str);
}

这段代码包含了两个函数:GetMemory 和 Test。我会一步一步地解释它们的功能和存在的问题。

GetMemory 函数

void GetMemory(char *p)  
{  
 p = (char *)malloc(100);  
}

这个函数接受一个 char* 类型的指针 p 作为参数,然后使用 malloc 分配100字节的内存,并将分配的内存地址赋值给 p。

问题:这个函数有一个关键的错误。它试图修改传递给它的指针 p 的值,但由于 C 使用传值调用(pass-by-value),所以函数内部的 p 只是一个外部指针的副本。因此,对 p 的任何修改都不会影响到外部指针。这意味着在 GetMemory 函数返回后,外部指针 str 仍然是 NULL。

Test 函数

void Test(void)  
{  
 char *str = NULL;  
 GetMemory(str);  
 strcpy(str, "hello world");  
 printf(str);  
}

这个函数首先定义了一个 NULL 指针 str,然后调用 GetMemory 函数试图分配内存给它。接着,它使用 strcpy 函数将字符串 “hello world” 复制到 str 指向的内存。最后,它使用 printf 打印这个字符串。

问题:由于 GetMemory 函数不能正确地修改 str 的值(如上面所述),str 仍然是 NULL。因此,在调用 strcpy 时会发生未定义行为(通常是程序崩溃),因为你试图将数据写入一个无效的(即未分配的)内存地址。

修正方法
要修正这个问题,你可以使用指针的指针(double pointer)来传递指针,这样你就可以在函数内部修改原始指针的值。下面是修正后的代码:

void GetMemory(char **p)  
{  
 *p = (char *)malloc(100);  
}  
  
void Test(void)  
{  
 char *str = NULL;  
 GetMemory(&str);  
 strcpy(str, "hello world");  
 printf("%s", str);  // 注意:这里应该使用 %s 而不是 str 直接作为参数。  
 free(str);  // 不要忘记释放分配的内存!  
}

这样,你就可以正确地在 GetMemory 函数内部为 str 分配内存,并在 Test 函数中使用这块内存了。

造成野指针原因:
①指针变量没有被初始化,任何刚创建的指针不会自动成为
NULL;
②指针被free或delete之后,没有置NULL;
③指针操作超越了变量的作用范围,比如要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

memcpy()和memmove()

memcpy()与memmove()一样都是用来拷贝src所指向内存内容前n个字节到dest
所指的地址上
不同的是,当src和dest所指的内存区域重叠时,memcpy可能无法正确处理,
而memmove()仍然可以正确处理,不过执行效率上略慢些。
memcpy()无论什么情况下,都是从前往后拷贝内存。当源地址在前,目的地址
在后,且两个区域有重叠时,会造成拷贝错误,达不到理想中的效果。
memmove()则分两种情况:目的地址在前,源地址在后的情况下,从前往后拷
贝内容。否则从后往前拷贝内容。无论什么情况都能达到理想中的效果。

在malloc的底层实现中,内存请求的流程通常如下:

检查空闲列表:首先,malloc会检查其内部的空闲列表,看是否有适合的内存块可直接使用。这个空闲列表通常包含之前分配过但后来释放的内存块。如果有合适的内存块,malloc就会直接返回这个内存块的地址,避免进行昂贵的系统调用。
向操作系统请求内存:如果在空闲列表中找不到合适的内存块,malloc就会向操作系统发出内存请求。在Unix-like系统中,这通常通过系统调用如brk、sbrk或mmap实现。这些系统调用会增加进程的堆大小,从而使其获得更多的内存。
内存分配和初始化:操作系统在接收到内存请求后,会查找可用的内存段,并将其标记为已分配。然后,操作系统会将这些内存段返回给malloc。接下来,malloc会将这些内存段加入到它的空闲列表中,以便将来快速分配。
返回分配的内存:最后,malloc会从空闲列表中取出新分配的内存块,并返回给用户。

内存泄漏(Memory Leak):在C语言中,当动态分配的内存(通过malloc, calloc, realloc等函数)不再需要,但未被释放时,就会发生内存泄漏。这意味着,这部分内存仍然被标记为“正在使用”,但实际上程序不再需要它。如果这种情况持续发生,程序将消耗越来越多的内存,即使它实际上并不需要。

内存溢出(Memory Overflow):这是一个更为严重的问题。当程序试图访问超出其分配内存范围的内存时,就会发生内存溢出。这可能是由于数组越界、错误地操作指针等原因造成的。内存溢出可能导致数据损坏,因为它会覆盖相邻内存区域的内容,甚至可能导致程序崩溃。

关系与区别:

结果:两者都会导致程序的不正常行为,但内存溢出通常更为严重,更容易导致程序崩溃。
原因:内存泄漏是因为忘记释放不再需要的内存,而内存溢出是因为访问了未被分配的内存。
持续性:内存泄漏是逐渐累积的,可能长时间内不会显现;而内存溢出通常是瞬间的,一旦发生,程序可能立即崩溃。
检测:内存泄漏可能在程序运行一段时间后才变得明显,而内存溢出通常在程序刚启动时或操作特定数据时就可以检测到。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值