C语言-内存管理
一、C 库函数 void *malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。
malloc 函数其实就是在内存(单片机堆中)中:找一片指定大小的空间,然后将这个空间的首地址给一个指针变量,这里的指针变量可以是一个单独的指针,也可以是一个数组的首地址, 这要看malloc函数中参数size的具体内容。我们这里malloc分配的内存空间在逻辑上是连续的,而在物理上可以不连续。我们作为程序员,关注的 是逻辑上的连续,其它的,操作系统会帮着我们处理的。
malloc和free函数是配对的,如果申请后不释放就是内存泄露;如果无故释放那就是什么都没有做,释放只能释放一次,如果释放两次及两次以上会出现错误(但是释放空指针例外,释放空指针其实也等于什么都没有做,所以,释放多少次都是可以的)
在c语言深度剖析上看到这样的解释:申请0字节的内存,函数并不返回NULL,而是返回一个正常的内存地址。但是你却无法使用这块大小为0的内存,将此比喻成尺子上的某个刻度,说刻度本身并没有长度,只有某两个刻度一起才能量出长度。这个时候判断语句:if(p == NULL)将不会起作用!
1.malloc的返回是void *,如果我们写成了: p = malloc(sizeof(int));间接的说明了(将void *转化给了int *,这不合理)
2.malloc的实参是sizeof(int),用于指明一个整形数据需要的大小,如果我们写成:
p = (int *)malloc(1); 那么可以看出:只是申请了一个字节的空间,如果向里面存放了一个整数的话,将会占用额外的3个字节,可能会改变原有内存空间中的数据
3.malloc只管分配内存,并不能对其进行初始化,所以得到的一片新内存中,其值将是随机的。一般意义上:我们习惯性的将其初始化为NULL。也可以用memset函数的。
举例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char *str; /* 最初的内存分配 */
str = (char *) malloc(15);
strcpy(str, "runoob");
printf("String = %s, Address = %u\n", str, str);
str = (char *) realloc(str, 25);/* 重新分配内存 */
strcat(str, ".com");
printf("String = %s, Address = %u\n", str, str);
free(str);//空间使用完后不释放会造成内存泄漏
return(0);
}
二、C 库函数 void *realloc(void *ptr, size_t size) 尝试重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小。
ptr -- 指针指向一个要重新分配内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。如果为空指针,则会分配一个新的内存块(相当于malloc),且函数返回一个指向它的指针。
size -- 内存块的新的大小,以字节为单位。如果大小为 0,且 ptr 指向一个已存在的内存块,则 ptr 所指向的内存块会被释放,并返回一个空指针。
如果分配内存减少,realloc仅仅改变索引的信息。
如果将分配的内存扩大,则有一下几种情况:
1. 如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回元指针。
2.如果当前内存段后面的空闲字节不够,那么就实验堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并把原来的数据块释放掉,返回新的内存块位置。
3.如果申请失败,将返回NULL,此时,原来的指针仍有效。
注意:如果调用成功,不管当前内存段后面的空闲空间是否满足要求,都会释放掉原来的指针,重新返回一个指针,虽然返回的指针有可能和原来的指针一样,即不能再次释放掉原来的指针
如果分配空间失败,realloc会返回NULL,但此时ptr指向的旧内存区域是没有释放的,需要我们调用free进行释放。因此我们在使用realloc的时候,不能直接将返回值赋值给ptr。正确的处理如下:
void *new_ptr = realloc(ptr, size);
if (!new_ptr)
{
//错误处理,如free(ptr)等
}
实际上,malloc(0)是合法的语句,会返还一个合法的指针,且该指针可以通过free去释放。这就造成了很多人对realloc()的错误理解,认为当size为0时,实际上realloc()也会返回一个合法的指针,后面依然需要使用free去释放该内存。比如
void *new_ptr = realloc(ptr, size);
if (!new_ptr)
{
free(ptr); return;
}
free(new_ptr);
return;
由于错误的认识,不去检验size是否为0,还是按照size不为0的逻辑处理,最后并free(new_ptr)或free(ptr)。这里就引入了double free的问题,造成程序崩溃。(实际调试的时候,如果size为0,那么返回的new_ptr为NULL)
实际上,传入的参数ptr为NULL或size为0都没有存在的必要,完全可以使用malloc或free来替代,这样,即可以避免踩到坑,也能给后续维护代码的人带来便利,方便理解。所以,使用realloc前,最好检查ptr以及size的值。
三、C 库函数 void *calloc(size_t nitems, size_t size) 分配所需的内存空间,并返回一个指向它的指针。malloc 和 calloc 之间的不同点是,malloc 不会设置内存为零,而 calloc 会设置分配的内存为零。
nitems -- 要被分配的元素个数。
size -- 元素的大小。
该函数返回一个指针,指向已分配的内存。如果请求失败,则返回 NULL。
很多人会疑问:既然calloc不需要计算空间并且可以直接初始化内存避免错误,那为什么不直接使用calloc函数,那要malloc要什么用呢?
实际上,calloc函数由于给每一个空间都要初始化值,那必然效率较malloc要低,并且很多情况的空间申请是不需要初始值的,这也就是为什么许多初学者更多的接触malloc函数的原因。
四、程序存储区域:堆,栈,静态存储区
栈:在程序中用于维护函数调用所有所需的维护信息( 函数参数,函数返回地址,局部变量,函数调用上下文),没有栈就没有函数与局部变量。由编译器自动分配释放,存放函数的参数值,局部变量等值。其操作方式类似于数据结构中的栈。

堆:是内存中一块巨大的内存空间,可由程序自由使用。堆中被程序申请使用的内存在程序主动释放前将一直有效。
堆上申请的空间内的数据可以用来在函数调用结束后将数据传递到函数外部。堆空间要由申请后才能获得。一般由程序员分配释放,若程序员不释放,则可能会引起内存泄漏。注堆和数据结构中的堆栈不一样,其类是与链表。
静态存储区:程序编译期间静态存储区的大小就已经确定全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
文字常量区:常量字符串就是放在这里的程序结束后由系统释放
程序代码区:存放函数体的二进制代码。
五、野指针、垂悬指针
野指针:访问一个已销毁或者访问受限的内存区域的指针,野指针不能判断是否为NULL来避免
垂悬指针:指针正常初始化,曾指向一个对象,该对象被销毁了,但是指针未制空,那么就成了悬空指针。
1.野指针产生的原因:
•指针定义时未被初始化:指针在被定义的时候,如果程序不对其进行初始化的话,它会随机指向一个区域,因为任意指针变量(出了static修饰的指针)它的默认值都是随机的
struct Student
{
char* name;
int number;
};
int main()
{
struct Student s;
strcpy(s.name, "Delphi Tang"); // OOPS! s.name随机指向一个区域
s.number = 99;
return 0;
}
•指针被释放时没有置空:我们在用malloc()开辟空间的时候,要检查返回值是否为空,如果为空,则开辟失败;如果不为空,则指针指向的是开辟的内存空间的首地址。指针指向的内存空间在用free()和delete释放后,如果程序员没有对其进行置空或者其他赋值操作的话,就会成为一个野指针
•void func()
{
int *ptr = new int[5];
delete []ptr;// 执行完delete后,ptr野指针
}
•void func(char* p)
{
printf("%s\n", p);
free(p);
}
int main()
{
char* s = (char*)malloc(5);
strcpy(s, "Delphi Tang");//数组越界
func(s);
printf("%s\n", s); // OOPS! 野指针
return 0;
}
•指针操作超越变量作用域:不要返回指向栈内存的指针或者引用,因为栈内存在函数结束的时候会被释放。
•void func()
{
int *ptr = nullptr;
{
int a = 10;
ptr = &a;
} // a的作用域到此结束
int b = *ptr; // ptr指向a,a已经被回收,ptr野指针
}
•char* func()
{
char p[] = "Delphi Tang";
return p;
}
int main()
{
char* s = func();
printf("%s\n", s); // OOPS!
return 0;
}
2.野指针的危害:
问题:指针指向的内容已经无效了,而指针没有被置空,解引用一个非空的无效指针是一个未被定义的行为,也就是说不一定导致错误,野指针被定位到是哪里出现问题,在哪里指针就失效了,不好查找错误的原因。
3.规避方法:
- 1.初始化指针的时候将其置为NULL,之后对其操作。
- 2.释放指针的时候将其置为NULL。

浙公网安备 33010602011771号