进阶C-动态内存管理

动态内存分配可以说是C语言中的超级大BOSS,他能使用的地方有许多许多,而且与free搭配使用。很多初学者,经常会忘记这个的使用。

而且它也是数据结构中很关键的一环。在各种的链表,顺序表等等,我们都需要去使用它。所以这是一个非常重要的一节

为什么存在动态内存分配

我们以尽掌握的内存开辟方式

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

但是上述的开辟方式有两个特点:

1.空间的开辟大小是固定的。

2.数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但比如我们在动态顺序表中,我们先开辟了一部分,但是在分配过程中,我们所分配的内存不够了,此时我们就需要malloc函数先临时开辟一个空间,之后再去使用,当使用完成之后在free释放掉。

int main(){    
    int len = 0;    
    printf("请输入长度:");    
    scanf("%d",&len);    
    //malloc只是动态申请一块连续的内存空间                            
	int* a = (int*)malloc(len * sizeof(int));
    return 0;
}
malloc()函数的优点
1.申请内存空间的长度能够比较灵活    
2.何时释放,完全由用户来掌控
malloc
void* malloc(size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。

如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

free
void free(void* ptr);

free函数用来释放动态开辟的内存。

如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

如果参数 ptr 是NULL指针,则函数什么事都不做。

如果频繁 malloc 但是忘记 free,此时就是内存泄漏

关于一些经典的动态分配

1.首先不能malloc()两次,或者free两次。动态分配两次也就是说你释放的时候只能释放一个,另一个并没有释放掉。即使你释放了两次。

2.free必须搭配malloc函数来使用。如果定义一个指针的地址,是不能释放掉的。这是错误的操作。

3.还有特殊规定,C++标准中规定了对 空指针进行 free/delete,都是合法的(无事发生)。所以当free一个内存之后,就把这个指针设为NULL。推荐这样做

int * p = (int*)malloc(sizeof(int));
free(p);
p = NULL;
//如果*p = 100;这就是一个未定义行为

calloc

void* calloc(size_t num,size_t size);

函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0.

与函数malloc的区别只在于calloc会返回地址之前把申请的空间的每个字节初始化为全0.

realloc

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

当我们发现我们申请的内存空间过大或者过小,我此时就可以使用realloc函数就可以对动态开辟内存大小的调整

ptr是要调整的内存地址

size调整之后新的大小

返回值为调整之后的内存起始位置

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到的空间上

一些常见的动态内存错误

对NULL指针的解引用操作

void test(){
    int *p = (int*)malloc(INT_MAX/4);
    *p = 20;
    free(p);
}

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

void test(){
    int i = 0;
    int *p = (int*)malloc(10*sizeof(int));
    if(NULL == p){
        exit(EXIT_FAILURE);//是个宏,返回1成功,返回0失败
    }
    for(i = 0;i <= 10;i++){
        *(p+i) = i;//当i是10的时候越界访问,*(p+i)相当于p[i],i为10时,数组下标越界
    }
    free(p);
}

使用free释放一块动态开辟内存的一部分

void test(){
    int *p = (int*)malloc(100);
    p++;
    free(p);//p不再指向动态内存的起始位置
}

free只能释放开辟的所有空间,这种行为是未定义行为

经典笔试题

第一题

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

在Test函数中,*str是实参,当通过GetMemory调用之后的str是形参,当函数结束结束之后,就不存在了,str依旧是一个空指针,将字符拷贝到一个空指针中,这将发生段错误,也就是未定义行为。应该修改为

void GetMemory(char **p){    
  *p = (char *)malloc(100);    
          
}         
void Test(void){    
  char *str = NULL;    
  GetMemory(&str);    
  strcpy(str, "hello world");    
  printf("%s\n",str);    
  free(str);                                                 
} 

第二题

char *GetMemory(void)
{
 char p[] = "hello world";
 return p;//返回的是数组的首元素地址
}
void Test(void)
{
 char *str = NULL;
 str = GetMemory();
 printf(str);
}

但是p[]数组仅仅是一个局部变量,函数结束,内存就释放了。应该修改为

  const char *GetMemory(void)    
  {    
    const char* p = "hello world";    
    return p;    
  }    
  void Test(void)    
  {    
    const char *str = NULL;                                        
    str = GetMemory();    
    printf("%s\n",str);       
  }

虽然内存可以访问,但是不可以修改

第三题

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

首先没有释放内存,并且在我们的调用过程中最好进行一个if对str的判断

第四题

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

释放完毕之后就不能在使用了,此时str就相当于一个野指针,虽然有结果打印出来,但是这个打印是非法的。

那么我们什么时候需要malloc内存?

当申请的内存空间比较大的时候,需要malloc

什么时候血药直接定义临时变量?

如果对于内存申请的性能要求较高的时候

柔性数组

只在C语言中存在

typedef struct Test{
 int i;
 int a[0];//a成员就是柔性数组成员
}Test;

typedef struct Test2{
 int i;
 int* a;
}Test;

int main(){
    Test* t = (Test*)malloc(sizeof(int)+sizeof(int)*10);
    t->i = 10;
    for(int i = 0;i < 10;i++){
        t->a[i] = i;
    }
    free(t);
    
    
    Test2* t2 = (Test2*)malloc(sizeof(int)+sizeof(int)*100);
    t2->i = 10;
    t2->a  = (int*)malloc(sizeof(int) * 10);
    free(t2->a);
    free(t2);
    return 0;
}

柔性数组的特点:

结构中的柔性数组成员前面必须至少一个其他成员。

sizeof 返回的这种结构大小不包括柔性数组的内存。

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

猜你喜欢

转载自blog.csdn.net/skrskr66/article/details/86581545