C语言-动态内存管理

C语言-动态内存管理,第1张

动态内存开辟函数 malloc与动态内存释放函数free:

void* malloc (size_t size);

使用malloc开辟空间时,需传递一个size_t类型的参数作为需要开辟的内存空间的大小(以字节为单位)

malloc函会返回一个void*类型的指针,指向开辟好的空间的起始位置

若开辟失败,则返回空指针NULL(所以返回后要做好检查。


)

使用时需注意,开辟好后需要保存好返回的起始位置地址,

在使用完该空间后,需要使用free函数对该空间进行释放,否则会造成内存泄露。


void free (void* ptr);

如果起始位置地址未被保存,则该空间将无法被释放。


free函数不能释放非动态内存开辟的空间,free函数不能释放动态内存开辟空间的一部分。


若传入free函数的是空指针,那么函数什么都不会做。


free函数其实是把对内存空间的 *** 作权换给了 *** 作系统,改空间可能还是存在,但是若free后依旧留着该空间的地址,这个地址就是野指针,为了避免野指针的出行,我们在free后应把改指针置为NULL。


free(p);
p=NULL;

calloc函数:

calloc函数可以动态开辟一块元素个数为为num,每个元素大小为size的内存空间,并将所有元素的值初始化为0。


其余和malloc相同。


void *calloc(size_t num,size_t size);

realloc函数:

realloc函数是对已开辟好的动态内存空间进行扩容

传入realloc函数的是需要调整的动态内存空间的起始地址,以及调整后的新的空间大小

void* realloc (void* ptr, size_t size);

如果在起始位置后有足够的空间扩容,那么扩容后返回的会是原来的地址;

如果在起始位置后没有足够的空间扩容,那么realloc函数会在内存空间寻找一块合适的位置,将原空间的内容移动至新的空间,并返回新的空间的其实地址;若扩容失败,则返回NULL。


常见的一些动态内存开辟的错误

1.对开辟失败返回的NULL指针进行解引用 *** 作:

void test()
{
 int *p = (int *)malloc(INT_MAX/4);
 *p = 20;//如果p的值是NULL,就会有问题
 free(p);
}

所以在对返回的地址进行解引用 *** 作前应先判断是否为空指针。


2.对动态开辟空间的越界访问

void test()
{
 int i = 0;
 int *p = (int *)malloc(10*sizeof(int));
 if(NULL == p)
 {
 exit(EXIT_FAILURE);
 }
 for(i=0; i<=10; i++)
 {
 *(p+i) = i;//当i是10的时候越界访问
 }
 free(p);
}

在 *** 作时应做好检查防止越界访问。


3. 对非动态开辟内存使用free释放
void test()
{
 int a = 10;
 int *p = &a;
 free(p);//ok?
}
4 使用free释放一块动态开辟内存的一部分
void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);//p不再指向动态内存的起始位置
}
5.对同一块动态内存多次释放
void test()
{
 int *p = (int *)malloc(100);
 free(p);
 free(p);//重复释放
}
6. 动态开辟内存忘记释放(内存泄漏)
void test()
{
 int *p = (int *)malloc(100);
 if(NULL != p)
 {
 *p = 20;
 }
}
int main()
{
 test();
 while(1);
}

所以应当做好内存释放,并正确地进行内存释放。


几个常见的笔试题

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

解析:传入getmemory函数的形参是对实参str的临时拷贝,在函数内对形参的 *** 作并不会影响到str,所以正确的做法应该是取str的地址传入函数中,这样在函数内对形参进行解引用就能 *** 作到str。


char *GetMemory(void) {
 char p[] = "hello world";
 return p; }
void Test(void) {
 char *str = NULL;
 str = GetMemory();
 printf(str);
}

解析:getmemory函数中的字符串常量p存放在栈区中,字符串首元素的地址虽然传入了str;但对函数的调用结束,这块空间就被销毁,str中存放的地址变成了野指针,对野指针进行解引用自然是不行的。


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

解析:getmemory函数和str没有问题 真正的问题是在使用完毕后没有对动态内存开辟的空间进行free *** 作。


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

解析:free后未将str置为NULL,导致str成为野指针,对野指针进行了解引用 *** 作。


对str是否为NULL的判断语句应放在动态开辟内存语句的后面。


free后将str置为NULL。


C/C++ 程序的内存开辟
C/C++程序内存分配的几个区域:
1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结
束时这些存储单元自动被释放。


栈内存分配运算内置于处理器的指令集中,效率很高,但是 分配的内存容量有限。


栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返 回地址等。


2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。


分 配方式类似于链表。


3. 数据段(静态区)(static)存放全局变量、静态数据。


程序结束后由系统释放。


4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。


普通的局部变量在栈区创建,出了函数就被销毁。


全局变量和被static修饰的局部变量存放在静态区,直到程序结束才会被销毁,所以被static修饰的局部变量的生命周期会变长。


柔性数组 结构的最后一个成员被允许是未知大小的数组,这个就被叫做柔性数组成员。


柔性数组的声明

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;
有些编译器可能会报错可以改成
typedef struct st_type
{
 int i;
 int a[];//柔性数组成员
}type_a;

柔性数组成员前面必须包含至少一个其他成员。


sizeof返回的结构的大小不包含柔性数组成员的大小。


包含柔性数组成员的结构的内存用malloc进行动态开辟,分配的内存应该大于结构的大小,以适应柔性数组的预期大小。


例如

typedef struct st_type
{
 int i;
 int a[0];
}type_a;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
这样开辟的柔性数组成员可以存放100个int类型的元素。


实现柔性数组的方法也可以使用结构体内部放一个指针,但相对于结构体内放指针,柔性数组释放动态开辟的内存只需要释放一次,更具优点。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/564163.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-03
下一篇 2022-04-03

发表评论

登录后才能评论

评论列表(0条)

保存