C基础语法(动态内存)

简介: C基础语法(动态内存)

为什么存在动态内存分配

一般我们在存放程序运行需要的数据时,会选择数组来进行存放,但数组的大小是不可变化的,设定了多少就是多少。程序的运行是动态的,我们往往并不知道产生的数据要有多大的空间来存放,如果用数组给的空间太大,会造成内存空间的极大浪费,运行效率也有所降低,给的太少又不够用,导致程序崩溃。因此我们需要一种动态的内存分配机制,如果检测到内存不够用,会自动开辟新的内存空间,用来存放数据。


动态内存分配函数

在C语言中,内存分配函数有3个,分别是 malloc() , calloc(), realloc()

在使用动态内存函数时切记,切记要引头文件 (无数血与泪的教训😭😭)

还有就是不再使用分配的内存后,要记得用free()来释放,否则会造成内存泄露的。

后面再解释内存泄漏,接下来介绍这3个内存开辟函数


malloc

void* malloc (size_t size)

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

如果开辟内存失败则返回空指针,因此在开辟空间时需要对返回的指针进行检查

//用malloc开辟10个int整型空间
int main()
{
  int* p = (int*)malloc(sizeof(int) * 10);
  if (NULL == p)
  {
    printf("fail\n");
    return 1;
  }
  free(p);
  p = NULL;
  return 0;
}
//正常的开辟流程

calloc

void* calloc (size_t num, size_t size);

calloc与malloc在功能上的差别就是calloc能够将开辟的空间都初始化为0,如果需要将开辟的空间初始化,可以选择calloc,另外在参数上也略有区别,calloc要传两个参数,一个参数是要开辟某类型空间的个数,另一个是要开辟的空间的类型。实际上就是把malloc的参数拆开来,上面的malloc( sizeof(int) *10 ) 变成calloc为 calloc( 10, sizeof(int) )

realloc

前面两个函数可能会让你产生一些疑惑,感觉和使用数组开辟的空间区别不大,并没有体现出动态开辟,那么realloc就能够解决这些问题,我们先看看realloc的声明

void* realloc (void* ptr,  size_t size)

由声明可知,第一个参数是一个指针,第二个参数是一个无符号整型数字,分别是什么含义呢。第一个参数需要传指向我们之前开辟过的空间的指针,在之前的例子中,我们尝试用malloc开辟了10个整型空间,并用指针变量p来维护这块空间,假设这10个整型空间不够使用了,我还想再扩增10个整型空间,且保存之前产生的数据,这个时候realloc函数就起到了很大作用。我们把之前维护开辟空间的指针p传过去,然后第二个参数传重新开辟空间的大小,我们想扩增10个容量,加上之前的就是20个空间,然后realloc会返回一个指针,返回的指针分为三种情况

1.空间开辟失败,返回一个空指针

2.原先空间的后面没有被使用的空间能够满足扩增的需求,那就返回维护原先空间的指针

3.原先空间的后面没有被使用的空间不满足扩增的需求,寻找一块新空间,返回新空间的起始地址

接下来我们详细分析一下

int main()
{
  int* p = (int*)malloc(sizeof(int) * 10);
  if (NULL == p)
  {
    printf("fail\n");
    return 1;
  }
// ......
   ......
//经过一段程序的使用,发现原先开辟的空间不够使用,要再扩增10个
 int *ps = (int*)realloc( p, sizeof(int)*20 )
 if( NULL == ps)
   {
    printf("fail\n");
    return 1;
   }
   p = ps; //如果还想让之前的指针p来维护这块新空间,可以进行这一步操作
   ps = NULL;
 //.......
  free(p);
  p = NULL;
  return 0;
}


造成内存泄漏的原因

很多程序一旦运行起来,除了设备停机,基本就一直处于运行状态,如果我们malloc了一块很大的空间,并且在使用完后没有用free()来释放,因为c/c++是没有自动回收机制的,这就导致我们malloc的这块空间在内存中一直处于被占用的状态,不用之后,程序不会再管维护这块空间的指针,就很可能导致维护空间的指针的丢失,从而失去了对这块空间的控制,在程序运行期间,再也找不到这块空间,也没法再使用这块空间,即造成内存泄漏。

而我们平时写的小程序,程序运行一会后就关闭了,此时程序所占用的全部空间归还操作系统,但我们要养成随用随释放的好习惯,不然以后接触大项目,内存泄漏很危险。

造成内存碎片化的原因

我们在用动态内存函数开辟空间时,使用的是内存的堆区,如果我们频繁的使用malloc

realloc来开辟内存空间,会把堆区分成一块一块的,每开辟的两块空间可能就夹杂着一些没用过的空间,而这些空间很细碎很难再被使用,会导致内存的利用率降低。


柔性数组

柔性数组的定义和特点

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员

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

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

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

typedef struct stu
{
  int age;
  char name[];
}S;
//char name[]就是一个柔性数组,它的大小可以是未定义的

柔性数组的使用和优势

//柔性数组的使用
struct S
{
    int n;
    int arr[];
};
int main()
{
    structu S s;
    //这样创建会导致柔性数组没有空间
    struct S* ps= (struct S*)malloc(sizeof(struct S) + 40);
    //40是给柔性数组arr预留的大小
}
struct S
{
    int n;
    int *arr;
};
int main() 
{
    struct S* ps = (struct S*)malloc( sizeof(struct S) );
    ps->arr = (int*)malloc(40);
}
//此为普通结构体的使用

柔性数组相对于普通结构体的优势

1.相比较与柔性数组,上面普通结构体会多消耗内存,也要开辟和释放两次

2.因为普通结构体要malloc两次,会造成内存碎片,导致内存利用率降低,运行速度略降低


使用动态内存函数常见的错误

1.对NULL指针进行解引用

造成这种情况的原因主要是不对内存开辟函数返回的指针进行检查,如果不检查,而恰好开辟失败,就造成对空指针的引用

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

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

这种情况就是对空间的使用不当,只开辟了10块空间,错误访问到第十一块空间

3.对非动态开辟的内存空间使用free()来释放空间

free()只能用来释放动态开辟的空间

void test()
{
  int a =0;
  int *p = &a;
  free(p);
}
//这样是错误的

4.对同一块动态内存多次释放

一块动态开辟内存释放一次就好,释放多次是错误的

5.使用free释放时只释放了其中一部分

很多时候我们动态开辟了一块空间,用指针p来维护,p指向这块空间的起始地址,但是在使用这块空间时,可能会导致p不再指向起始位置,在不把p返回到起始位置时就把p释放,这样是错误的。

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

6.忘记释放已开辟的内存(内存泄漏)

可能很多同学说自己早已经记住了要释放已开辟的内存,不会造成内存泄露的,事实上我们的逻辑并没有我们认为的那么严谨,不妨看看下面一段程序

int test()
{
  int* p = (int*)malloc(sizeof(int) * 2);
  if (NULL == p)
  {
    printf("fail\n");
    return -1;
  }
  scanf("%d %d", p, p + 1);
  if ( *p > *(p+1) )
  {
    printf("较大的数是:");
    return *p;
  }
  else
  {
    printf("较大的数是:");
    return *(p+1);
  }
  free(p);
  p = NULL;
}
int main()
{
  int m = test();
  printf("%d", m);
  return 0;
}

上面是实现返回较大值的简单程序,如果你感觉这段程序没有毛病的话,那么非常抱歉,你已经造成了内存泄漏,先分析一下原因,我们在test函数内开辟了两个int 类型的空间,并输入两个值进行对比,到目前为止都没有毛病,毛病就出在我们在进行对比后返回一个返回值,直接就离开了test()函数,在离开之前我们可没有对p进行free释放啊,而p又是在test内部定义的,其只能在test内部使用,离开test,p就还给了内存,变成了野指针,也就是说我们再也无法通过p来找到这块未释放的空间,在程序运行期间,这块空间就被泄露了

是不是感觉内存泄漏没有那么简单,如果你一眼就看出了问题,那么你的洞察和逻辑能力很强,很聪明,要继续保持这份警觉,如果你没有看出,那么现在你已经被种上了这份警觉

以后多留意观察,不可盲目自信。


目录
相关文章
|
7月前
|
安全 C语言
【进阶C语言】动态内存分配
函数参数:根据用户的需求需要开辟多大的字节空间,为无符号的字节。 返回值:malloc函数成功开辟内存后,会返回该内存的起始地址,可以根据需要强制转换成任意的类型;若开辟空间失败,则会返回空指针(NULL)。 头文件:#include<stdlib.h>
29 0
|
7月前
|
编译器 C语言
【C语言进阶】动态内存管理(上)
【C语言进阶】动态内存管理(上)
|
7月前
|
程序员 编译器 C语言
【C语言进阶】动态内存管理(下)
【C语言进阶】动态内存管理(下)
|
2月前
|
程序员 编译器 C语言
【C语言进阶】动态内存管理
【C语言进阶】动态内存管理
|
5月前
|
程序员 编译器 C语言
『C语言进阶』动态内存管理
『C语言进阶』动态内存管理
|
9月前
|
编译器 C语言
【C语言进阶】动态内存管理(一)
【C语言进阶】动态内存管理(一)
46 0
|
9月前
|
存储 程序员 C语言
【C语言进阶】动态内存管理(二)
【C语言进阶】动态内存管理(二)
52 0
|
9月前
|
安全 C语言
【C语言进阶(八)】动态内存管理
【C语言进阶(八)】动态内存管理
|
10月前
|
编译器 C语言
进阶C语言——动态内存管理
进阶C语言——动态内存管理
|
11月前
|
存储 程序员 编译器
【c语言进阶】动态内存管理知识大全(上)一
【c语言进阶】动态内存管理知识大全(上)一
78 0