动态内存开辟函数 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++程序内存分配的几个区域:
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类型的元素。
实现柔性数组的方法也可以使用结构体内部放一个指针,但相对于结构体内放指针,柔性数组释放动态开辟的内存只需要释放一次,更具优点。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)