malloc、free函数及memset函数用法详解

最近在力扣刷题时,发现普通的创建数组很容易引起编译器报错。
在这里插入图片描述
而使用到动态内存分配malloc()函数和初始化函数memset()可以避免这类问题的发生。本文将详细介绍一下两个函数的
在这里插入图片描述

malloc()

malloc函数的原型:

void *)malloc(size_t size)

malloc 函数接受一个 size_t 类型的参数 size,表示要分配的内存块的大小(以字节为单位)。它返回一个指向分配内存块起始位置的 void* 类型指针,即通用指针。内存分配成功之后,malloc函数返回这块内存的首地址,你需要一个指针来接受这个地址。

int *ptr = (int *) malloc(sizeof(int) * length);

malloc分配了内存,但没有为它指定名字。然而,它却可以返回那块内存第一个字节的地址。
因此,可以把那个地址赋值给一个指针变量,并使用该指针来访问那块内存。
因为char代表一个字节,所以传统上曾将malloc()定义为指向char的指针类型。

然而,ANSIC标准使用了一个新类型:指向void的指针。这一类型被用作“通用指针”。
函数malloc()可用来返回数组指针、结构指针等等,因此一般需要把返回值的类型指派为适当的类型。

在ANSIC中,为了程序清晰应对指针进行类型指派,但将void 指针值赋值给其他类型的指针并不构成类型冲突。
需要注意的是,malloc 函数分配的内存块是未初始化的,其中的内容是不确定的。如果你需要初始化内存块为特定的值,可以使用 calloc 函数来分配并初始化内存。

另外,如果 malloc 函数无法成功分配所需的内存块,它将返回 NULL。因此,在使用分配的内存之前,应该始终检查返回的指针是否为 NULL,以确保内存分配成功。
我们使用malloc()来创建一个 数组。可以在程序运行时使用malloc()请求一个存储块,另外还需要一个指针来存放该块在内存中的位置。

例如,如下代码:

double * ptd;
ptd = (double * ) malloc (30 * sizeof(double));

这段代码请求30个double类型值的空间,并且把ptd指向该空间所在位置。

注意:ptd是作为指向一个double类型值的指针声明的,而不是指向30个double类型值的数据块的指针。

记住:数组的名字是它第一个元素的地址。
因此,如果令ptd指向一个内存块的第一个元素,就可以像使用数组名一样使用它。
也就是说,可以使用表达式ptd[0]来访问内存块的第一个元素,pd[1]来访问第二个元素,依此类推。
正如前面所学,可以在指针符号中使用数组名,也可以在数组符号中使用指针。

现在,创建一个数组有三种方法:
1.声明一个数组,声明时用常量表达式指定数组维数,然后可以用数组名访问数组元素。
2.声明一个变长数组,声明时用变量表达式指定数组维数,然后用数组名来访问数组元素(这是C99的一个特性)。
3.声明一个指针,调用malloc(),然后使用该指针来访问数组元素。

一般地,对应每个malloc()调用,应该调用一次free()。
函数free()的参数是先前malloc()返问的地址,它释放先前分配的内存。
这样,所分配内存的持续时间从调用malloc()分配内存开始,到调用free()释放内存以供再使用为止。free()的参数应是一指针,指向由malloc()分配的内存块,

我们来看一个理想模型,可以认为程序将它的可用内存分成了三个独立的部分:
一个是具有外部链接的、具有内部链接的以及具有空链接的静态变量的:
一个是自动变量的:

(静态变量):
在编译时就已经知道了静态存储时期存储类变量所需的内存数量,存储在这一部分的数据在整个程序运行期间都可用。
这一类型的每个变量在程序开始时就已存在,到程序结束时终止。
(动态变量):
然而,一个自动变量在程序进入包含该变量定义的代码块时产生,在退出这一代码块时终止。
因此,伴随着程序对函数的调用和终止,自动变量使用的内存数量也在增加和减少。
典型地,将这一部分内存处理为一个堆栈。这意味着在内存中,新变量在创建时按顺序加入,在消亡时按相反顺序移除。
(动态内存分配):
动态分配的内存在调用malloc()或相关函数时产生,在调用free()时释放。
由程序员而不是一系列固定的规则控制内存持续时间,因此内存块可在一个函数中创建,而在另一个函数中释放。(malloc 可以跨函数调用)
由于这点,动态内存分配所用的内存部分可能变成碎片状,也就是说,在活动的内存块之间散布着未使用的字节片。不管怎样,使用动态内存往往导致进程比使用堆栈内存慢。

free()

malloc与free函数如影随形,free函数用于释放通过动态分配函数(如malloc、calloc或realloc)分配的内存块。它的函数原型如下:

void free(void* ptr);

free函数接受一个指向动态分配内存块的指针作为参数,将该内存块释放,并返回给内存管理系统以供重用。

使用free函数时需要注意以下几点:

  1. 传递给free函数的指针必须是通过动态分配函数分配的有效指针。如果传递给free的指针是NULL,则函数不执行任何操作。
  2. 一旦内存被释放,就不应再访问该内存块,否则会导致未定义的行为。
  3. 不要试图释放静态分配的内存(如全局变量或自动变量),这将导致未定义的行为。
  4. 可以多次调用free函数释放同一块内存,但是这样做是不推荐的,因为重复释放同一块内存可能导致问题。
    以下是一个示例,展示如何使用malloc和free函数分配和释放内存:
int* ptr = (int*)malloc(sizeof(int)); // 分配内存
if (ptr != NULL) {
    *ptr = 10; // 使用内存
    printf("Value: %d\n", *ptr);
    free(ptr); // 释放内存
}

在调用 free 函数释放内存后,指针本身并不会消失。指针变量仍然存在,但是它不再指向有效的内存块。
当你调用 free 函数释放内存时,它会将该内存块返回给内存管理系统以供重用,但并不会对指针变量本身做任何修改。指针仍然指向原来的地址,但这块地址不再是有效的动态分配内存。在释放内存后,访问已释放的内存块将导致未定义的行为,可能会引发访问非法内存的错误。

为了避免访问已释放的内存块,一种良好的实践是在释放内存后将指针设置为 NULL,示例如下:

int* ptr = (int*)malloc(sizeof(int)); // 分配内存
if (ptr != NULL) {
    // 使用内存
    *ptr = 10;
    
    // 释放内存
    free(ptr);
    ptr = NULL; // 将指针设置为 NULL
}

// 现在 ptr 是一个空指针,指向 NULL

可以将 free 理解为解除内存和指针之间的联系。当你调用 free 函数时,它将释放动态分配的内存块,并将该内存块返回给内存管理系统以供重用。这个操作会使指针与之前指向的内存块之间的联系断开。

在调用 free 之后,指针仍然存在,但它不再指向有效的内存块。这意味着你不能再通过该指针来访问已释放的内存块,因为该内存块已经不再属于你的程序。尝试访问已释放的内存块将导致未定义的行为,可能会产生不可预测的结果。

通过释放内存并解除与指针之间的联系,你告诉内存管理系统可以将该内存块重新分配给其他需要它的部分。这有助于有效地管理内存并避免内存泄漏。

记住,在调用 free 后,为了避免使用野指针,最好将指针设置为 NULL,以表示它不再指向有效的内存块。这样,如果你意外地尝试使用指针,程序会引发空指针异常,从而提醒你检查和修复代码中的问题。

memset()

memset是一个初始化函数,作用是将某一块连续的内存中全部初始化为指定的值。

void *memset(void *s, int c, size_t n); 

s指向要填充的内存块。
c是要被设置的值。
n是要被设置该值的字符数。
返回类型是一个指向存储区s的指针。
总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。

memset可以方便的清空一个结构类型的变量或数组
如:struct sample_struct
{
char csName[16];
int iSeq;
int iType;
};

对于变量:struct sample_strcut stTest;
一般情况下,清空stTest的方法:
stTest.csName[0]=‘/0’;
stTest.iSeq=0;
stTest.iType=0;

用memset就非常方便:
memset(&stTest,0,sizeof(struct sample_struct));

如果是数组:struct sample_struct TEST[10];
则memset(TEST,0,sizeof(struct sample_struct)*10);

常见错误:
第一:memset函数按字节对内存块进行初始化,所以不能用它将int数组初始化为0和-1之外的其他值(除非该值高字节和低字节相同)。

第二:memset(void *s, int ch,size_t n);中ch实际范围应该在0~~255,因为该函数只能取ch的后八位赋值给你所输入的范围的每个字节,比如int a[5]赋值memset(a,-1,sizeof(int )*5)与memset(a,511,sizeof(int )*5) 所赋值的结果是一样的都为-1;因为-1的二进制码为(11111111 11111111 11111111 11111111)而511的二进制码为(00000000 00000000 00000001 11111111)后八位都为(11111111),所以数组中每个字节,如a[0]含四个字节都被赋值为(11111111),其结果为a[0](11111111 11111111 11111111 11111111),即a[0]=-1,因此无论ch多大只有后八位二进制有效,而后八位二进制的范围在(0~255)中改。而对字符数组操作时则取后八位赋值给字符数组,其八位值作为ASCII码。

第四: 过度使用memset.

char buffer[4];
memset(buffer,0,sizeof(char)*4);
strcpy(buffer,"123");

//"123"中最后隐藏的'\0'占一位,总长4位。

这里的memset是多余的. 因为这块内存马上就被全部覆盖,清零没有意义.

另:以下情况并不多余,因某些编译器分配空间时,内存中默认值并不为0:

char buffer[20];
memset(buffer,0,sizeof(char)*20);
memcpy(buffer,"123",3);

//这一条的memset并不多余,memcpy并没把buffer全部覆盖,如果没有memset,

//用printf打印buffer会有乱码甚至会出现段错误。

//如果此处是strcpy(buffer,"123");便不用memset,

//strcpy虽然不会覆盖buffer但是会拷贝字符串结束符

memset函数在初始化处理时非常方便,但也有其局限性,比如要注意初始化数值,要注意字节数等等。当然,直接选择用for循环或while循环来进行初始化也是可以的,只不过memset更快捷一些。

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
mallocfreeC语言中用于动态内存分配和释放的函数malloc函数用于在堆上分配一块指定大小的内存空间。它的原型定义在stdlib.h头文件中: void* malloc(size_t size); malloc函数接受一个参数size,表示需要分配的内存大小(单位是字节)。它返回一个void类型的指针,指向分配的内存块的起始地址。如果分配失败,malloc函数返回NULL。 下面是一个使用malloc函数动态分配内存的示例: #include <stdio.h> #include <stdlib.h> int main() { int *ptr; int n = 5; ptr = (int*) malloc(n * sizeof(int)); if(ptr == NULL) { printf("内存分配失败\n"); return 1; } for(int i = 0; i < n; i++) { ptr[i] = i + 1; } for(int i = 0; i < n; i++) { printf("%d ", ptr[i]); } free(ptr); return 0; } 在上面的示例中,我们使用malloc函数动态分配了一个能容纳5个int类型数据的内存块。然后,我们使用指针ptr来操作这个内存块。最后,使用free函数释放了这个内存块,以便将其返回给系统。 free函数用于释放通过malloc函数分配的内存空间。它的原型定义在stdlib.h头文件中: void free(void* ptr); free函数接受一个参数ptr,表示要释放的内存块的起始地址。调用free函数后,系统将回收这块内存空间,可以重新被其他程序使用。 需要注意的是,调用free函数后,释放的内存空间不再属于我们的程序,访问已释放的内存将导致未定义的行为,甚至可能引发程序崩溃。因此,使用free函数后,建议将指针ptr设置为NULL,以避免误用。 总结: - malloc函数用于动态分配内存空间,返回指向分配内存的指针。 - free函数用于释放动态分配的内存空间,将其返回给系统。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值