C语言的动态内存分配及动态内存分配函数详解

目录
  • malloc
    • malloc的使用:
  • free
  • calloc
    • calloc的使用:
  • realloc
    • realloc的使用改进:
    • realloc的另一种用法:
  • 常见的动态内存错误
    • 对空指针的解引用操作
    • 对动态开辟空间的越界访问
    • 对非动态开辟内存使用free释放
    • 使用free释放一块动态开辟内存的一部分
    • 对同一块动态内存多次释放
    • 动态开辟内存忘记释放(内存泄露)
  • 找出下面问题:
    • T1:
    • T2:
    • T3:
    • T4:
  • 柔性数组
    • 柔性数组的定义
    • 柔性数组的特点:
  • 总结

malloc

void *malloc( size_t size );

Tips:这里的size代表的是字节的大小

malloc的使用:

//malloc的使用
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
    int* str = 0;
    int* p = 0;
    str = (int*)malloc(10*sizeof(int));//开辟十个整型空间
    if (NULL == str)
    {
        printf("%s\n", strerror(errno));//若开辟失败
        //使用报错函数strerror(errno)    要引用头文件<string.h>
    }
    else
    {
        p = str;
    }
    free(p);
    p = NULL;
    return 0;
}

free

释放申请的内存空间,例:free(p)

当释放后,虽然p中的值还在,不变,但p就为野指针了。所以建议释放后将p设置为空指针。(p=NULL)

calloc

calloc:开辟并且初始化为0的数组。

void* calloc(size_t num,size_t size)

  • num——元素个数
  • size——元素大小

成功的话返回地址,失败返回空指针NULL

calloc的使用:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
    int* str = 0;
    int* p = 0;
    str = (int*)calloc(10,sizeof(int));
    if (NULL == str)
    {
        printf("%s\n", strerror(errno));
    }
    else
    {
        p = str;
    }
    free(p);
    p = NULL;
    return 0;
}

realloc

可开辟空间,也可以调整空间。

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

  • memblock——要开辟空间的指针类型
  • size——要开辟的字节大小

p=(int)realloc(p,80)*——这样子写也是有风险的。

风险:为了避免可能会把增容的后面的已有的内存空间给覆盖掉,所以会在另一块大小足够的地方开辟空间,然后把原来的数据转移到新的空间上。并且把原来的内存空间给释放掉。

若realloc调整空间失败,则返回NULL。原来的数据也没有了。

realloc的使用改进:

int* ptr=(int*)realloc(p,80);
if(NULL!=ptr)
{
    p=ptr;//这样子能够保证确定了不为空指针后才正式传给p,相当于没有了会失去原来数据的风险
}

realloc的另一种用法:

int* p=(int*)realloc(NULL,40);

这种写法相当于malloc

常见的动态内存错误

对空指针的解引用操作

将malloc函数开辟一个贼大的空间,INT_MAX,此时会有一个空指针,进行判断,如果为空指针就立马结束这个程序了。不要出问题(ps:这里的INT_MAX的使用要引用头文件limits.h)

所以要判断是不是空指针,是的话就中断,例:

//错误写法
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
    int i = 0;
    int* p = (int*)malloc(INT_MAX);
    for (i = 0; i < 5; i++)
    {
        *(p + i) = i;
    }
    return 0;
}

//正确写法:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
    int i = 0;
    int* p = (int*)malloc(INT_MAX);
    if (p == NULL)
    {
        printf("%s\n", strerror(errno));//这里是将错误报出来
        return 0;//发现是空指针,提前结束
    }
    for (i = 0; i < 5; i++)
    {
        *(p + i) = i;
    }
    return 0;
}
 

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

不可以不申请即使用动态内存空间,会报错的。

Tips:没有开辟的空间是不能使用的

对非动态开辟内存使用free释放

int main()
{
    int p=0;
    int* a=&p;
    free(a);//这个样子是错误的
    return 0;
}

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

开辟动态空间的时候,一定要把起始位置给用变量存好,否则到时会无法释放内存。

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

//正确写法:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int i = 0;
    int* p = (int*)malloc(10 * sizeof(int));
    //正确写法:
    for (i = 0; i < 5; i++)
    {
        *(p + i) = i;
    }
    free(p);
    p=NULL;
    return 0;
}

//错误写法:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int i = 0;
    int* p = (int*)malloc(10 * sizeof(int));
    //错误写法:
    for (i = 0; i < 5; i++)
    {
        *p = i;
        p++;//这里会改变p的原始位置,使得无法指向一开始开辟动态内存空间的位置,最终报错
    }
    free(p);
    p = NULL;
    return 0;
}

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

一块空间释放后不可再释放,但释放完后p置为空指针再次释放时不会报错。

Q:free空指针时会有问题么?

A:不会,因为一块空间释放后就不能再次释放了,所以每次free完后记得置为空指针。

动态开辟内存忘记释放(内存泄露)

即使在函数中开辟内存空间也要记得释放。因为出了函数在外面想释放也无法释放。

但如果返回首元素的地址,free了也行,就是无论怎么样,一定要释放。

在任何地方开辟的内存空间都最好要释放。

找出下面问题:

T1:

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

int main()
{
    Test();
    return 0;
}

出现的问题:

在这里str是空指针,而p只是新建的一个形参,运行完函数后无法返回p不存在了,但是内存空间还未被释放,而这个空间的地址此时是没有人能够知道的。也并不能将str里面的NULL改变,所以在strcpy时会出错,因为str此时为NULL指针,会造成非法访问内存,程序会崩溃。

而且在使用过程中只进行了动态内存的开辟,没有进行动态内存的释放,可能会造成动态内存泄露。

改进方法:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

int main()
{
    Test();
    return 0;
}

函数的栈帧与创建:p尽管销毁,因为会先把p里面的值放入到寄存器中,寄存器里面不会销毁,之后再从寄存器位置传进去str。

T2:

出现的问题:

返回栈空间地址问题:

这里虽然能把p的地址传回去,但是在函数运行完后在函数里面创建的数据会被销毁,也就是说虽然能通过指针找到原来的内存所指向的地方,但是数据都以被销毁。

注意!!!

这样是可以的,因为返回的是栈空间的变量而不是栈空间的地址。

总结:

在创造函数如果返回地址而不是返回值,在用的时候可能依然是在函数内的值,但也有很大可能不是,可能不是的原因是有关函数栈帧方面,如果在引用地址前再写上一段例如:"printf("23333\n");",可能会导致覆盖掉原来地址上的数据,所以无法通过传址来输出真正的值,因为会被覆盖掉。

T3:

出现的问题:

除了free没有太大毛病了。这里能够打印出hello。

T4:

出现的问题:

这里的free其实是把动态内存空间还给系统了,但是str的话没有定为空指针,仍然存着当初指向开辟的内存空间的地址,那么就还可以通过str找到当初开辟的内存空间,只是这个时候因为释放(free)str了,所以此时没有访问空间的权限,也就无法将world拷贝到str所指向的空间。

正确改法:

所以,在每次free后面都要记得设置为空指针。

柔性数组

在c99中,结构体中的最后一个元素是允许未知大小的数组,这就叫做【柔性数组】成员。

柔性数组的定义

//写法一:
struct s1
{
    int n;
    int arr[0];//大小是未指定
}
//写法二:
struct s2
{
    int n;
    int arr[];//大小是未指定
}
//总会有一种写法编译器不报错
 

Tips:在计算包含柔性数组大小的时候,柔性数组是不计算在大小里面的。(可以写一个来试一下)

柔性数组的特点:

  • 柔性数组前至少需要一个其他成员
  • sizeof返回的这种结构大小不包括柔性数组的内存
  • 包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

柔性数组的开辟(自己先写)

包含柔性数组的结构体不可以直接创建,而是要有malloc来开辟空间。

//写柔性数组的方法一:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct p
{
    int i;
    int arr[];
};

int main()
{
    struct p* cmp = (struct p*)malloc(sizeof(struct p) + 80);//这里是开辟了一共84个字节空间,分给arr数组80个字节空间
    free(p);
    p = NULL;
    return 0;
}

//写柔性数组的方法二:(先开辟整个的,再开辟数组的)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct p
{
    int i;
    int* arr;//这样才能在方法二中使用
};

int main()
{
    struct p* cmp = (struct p*)malloc(sizeof(struct p));
    cmp->i = 10;
    cmp->arr = (int*)malloc(80);//从数组开始,再次开辟80个字节空间
    free(p);
    p = NULL;
    return 0;
}
 

第二种方案(劣势):

1.开辟和释放的次数多,容易出错

2.频繁多次开辟内存,会有内存碎片出现,可能会导致内存的使用效率不高

第一种方案优势:

1.方便释放

2.减少内存碎片的出现

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • c语言动态内存分配知识点及实例

    c语言怎么实现动态内存分配 我们经常会预先给程序开辟好内存空间,然后进行操作. int arr[5] ; 对这个数组我们在定义的时候必须给提前开辟好空间,并且在程序执行的过程中,这个开辟的内存空间是一直存在的,除非等到这个函数执行完毕,才会将空间释放.有个问题就是这个数组在程序中无法被修改. 这些问题给我们造成了一些使用上的不方便,所以,C中提供了malloc()函数. 关于malloc()函数,这个函数它接受一个参数:就是所需的内存的字节数.然后malloc()找到可用内存中那一个大小适合的块

  • C语言动态内存分配函数的实现

    在C中我们开辟内存空间有两种方式 : 1.静态开辟内存 :例如: int a;int b[10]; 这种开辟内存空间的特点是 所开辟的内存是在栈中开辟的固定大小的 ,如a是4字节 ,数组b是40字节 ,并且数组在申明时必须指定其长度 , 如果是全局数组的话,内存是在编译时分配好的,如果是局部变量数组的话,运行时在栈上静态分配内存.不管是全局数组还是局部数组,它们都有一个特点,那就是数组大小是确定的,是代码中写死的.那如果我们想在程序运行时才确定一个数组的大小 , 前两种在栈上分配内存的方法显然是

  • C语言 动态内存分配详解

    C语言 动态内存分配详解 动态内存分配涉及到堆栈的概念:堆栈是两种数据结构.堆栈都是数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除. 栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈. 堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表. \在C语言中,全局变量分配在内存中的静态存储区,非静态的局部变量(包括形参)是分配在内存的动态存储区,该存储区被

  • C语言中关于动态内存分配的详解

    目录 一.malloc 与free函数 二.calloc 三.realloc 四.常见的动态内存的错误 [C语言]动态内存分配 本期,我们将讲解malloc.calloc.realloc以及free函数. 这是个动态内存分配函数的头文件都是 <stdlib.h>. c语言中动态分配内存的函数,可能有些初学c语言的人不免要问了:我们为什么要通过函数来实现动态分配内存呢? 首先让我们熟悉一下计算机的内存吧!在计算机的系统中大致有这四个内存区域: 1)栈:在栈里面储存一些我们定义的局部变量以及形参(

  • C语言 动态内存分配的详解及实例

    1. 动态内存分配的意义 (1)C 语言中的一切操作都是基于内存的. (2)变量和数组都是内存的别名. ①内存分配由编译器在编译期间决定 ②定义数组的时候必须指定数组长度 ③数组长度是在编译期就必须确定的 (3)但是程序运行的过程中,可能需要使用一些额外的内存空间 2. malloc 和 free 函数 (1)malloc 和 free 用于执行动态内存分配的释放 (2)malloc 所分配的是一块连续的内存 (3)malloc 以字节为单位,并且返回值不带任何的类型信息:void* mallo

  • C语言动态内存分配的详解

    C语言动态内存分配的详解 1.为什么使用动态内存分配 数组在使用的时候可能造成内存浪费,使用动态内存分配可以解决这个问题. 2. malloc和free C函数库提供了两个函数,malloc和free,分别用于执行动态内存分配和释放. (1)void *malloc(size_t size); malloc的参数就是需要分配的内存字节数.malloc分配一块连续的内存.如果操作系统无法向malloc提供更多的内存,malloc就返回一个NULL指针. (2)void free(void *poi

  • jQuery动态添加.active 实现导航效果代码思路详解

     代码思路: 页面4: 页面5: 代码思路: 通过jq获取你打开页面的链接  window.location.pathname: 在HTML中给自己的li加入一个ID id的命名与网址链接中的href相同 通过jq包含方法找到相对应的li给他加入active类名 然后..就没有然后了... jq代码: $(function () { var li = $(".title_ul").children("li"); for (var i = 0; i < li.l

  • JDK动态代理过程原理及手写实现详解

    目录 JDK动态代理的过程 手写实现JDK动态代理 创建MyInvocationHandler接口 创建MyClassLoader类加载器 创建代理类 使用自定义动态代理类 创建接口 创建被代理接口 创建代理接口 客户端调用 生成源代码 JDK动态代理的过程 JDK动态代理采用字节重组,重新生成对象来替代原始对象,以达到动态代理的目的. JDK中有一个规范,在ClassPath下只要是$开头的.class文件,一般都是自动生成的. 要实现JDK动态代理生成对象,首先得弄清楚JDK动态代理的过程.

  • C语言自定义数据类型的结构体、枚举和联合详解

    结构体基础知识 首先结构体的出现是因为我们使用C语言的基本类型无法满足我们的需求,比如我们要描述一本书,就需要书名,作者,价格,出版社等等一系列的属性,无疑C语言的基本数据类型无法解决,所以就出现了最重要的自定义数据类型,结构体. 首先我们创建一个书的结构体类型来认识一下 struct Book { char name[20]; char author[20]; int price; }; 首先是struct是结构体关键字,用来告诉编译器你这里声明的是一个结构体类型而不是其他的东西,然后是Boo

  • C语言sizeof和strlen的指针和数组面试题详解

    目录 一.概念 sizeof: strlen: 二.例题及解析 2.1 一维数组 2.2 字符数组 2.3 二维数组 三.总结 一.概念 sizeof: sizeof操作符的结果类型为size_t,(它在头文件用typedfe定义为unsigned int类型),计算的是分配空间的实际字节数.sizeof是运算符,可以以类型.函数.做参数 . strlen: strlen结果类型也为size_t(size_t strlen( const char *string )),但strlen是计算的空间

  • R语言函数详解及实例用法

    函数是一组组合在一起以执行特定任务的语句. R 语言具有大量内置函数,用户可以创建自己的函数. 在R语言中,函数是一个对象,因此R语言解释器能够将控制传递给函数,以及函数完成动作所需的参数. 该函数依次执行其任务并将控制返回到解释器以及可以存储在其他对象中的任何结果. 函数定义 使用关键字函数创建 R 语言的函数. R 语言的函数定义的基本语法如下 function_name <- function(arg_1, arg_2, ...) { Function body } 函数组件 函数的不同部

  • C语言 模拟实现memcpy与memmove函数详解

    目录 一.memcpy函数的介绍 1.函数的声明 2.函数功能与注意事项 3.函数的使用 二.模拟实现memcpy函数 1.模拟分析 2.模拟实现 三.memmove函数的介绍 1.函数的声明 2.为什么会有memmove函数 3.函数功能与注意事项 4.函数的使用 四.模拟实现memmove函数 1.模拟分析 2.模拟实现 一.memcpy函数的介绍 1.函数的声明 void * memcpy ( void * destination, const void * source, size_t

  • C语言头文件<string.h>函数详解

    目录 1. strlen —— 求字符串长度 1.1 strlen 的声明与用处 1.2 strlen 的用法 1.3 strlen 的模拟实现 2. strcpy —— 字符串拷贝 2.1 strcpy 的声明与用处 2.2 strcpy 的用法 2.3 strcpy 的模拟实现 3. strcmp —— 字符串比较 3.1 strcmp 的声明与用处 3.2 strcmp 的用法 3.3 strcmp 的模拟实现 4. strcat —— 字符串追加 4.1 strcat 的声明与用处 4.

  • C语言文件操作中 fgets与fputs 函数详解

    C语言文件操作中 fgets.fputs 函数详解 先给出api fgets 语法: #include <stdio.h> char *fgets( char *str, int num, FILE *stream ); 函数fgets()从给出的文件流中读取[num - 1]个字符并且把它们转储到str(字符串)中. fgets()在到达行末时停止,在这种情况下,str(字符串)将会被一个新行符结束. 如果fgets()达到[num - 1]个字符或者遇到EOF, str(字符串)将会以nu

  • C语言实现opencv提取直线、轮廓及ROI实例详解

    一.Canny检测轮廓 在上一篇文章中有提到sobel边缘检测,并重写了soble的C++代码让其与matlab中算法效果一致,而soble边缘检测是基于单一阈值的,我们不能兼顾到低阈值的丰富边缘和高阈值时的边缘缺失这两个问题.而canny算子则很好的弥补了这一不足,从目前看来,canny边缘检测在做图像轮廓提取方面是最优秀的边缘检测算法. canny边缘检测采用双阈值值法,高阈值用来检测图像中重要的.显著的线条.轮廓等,而低阈值用来保证不丢失细节部分,低阈值检测出来的边缘更丰富,但是很多边缘并

  • python内存监控工具memory_profiler和guppy的用法详解

    python2.7在内存管理上相比python3还是有些坑的,其释放后的内存仍然保留在python的内存池中,不被系统所用.python循环引用的变量不会被回收,这会导致程序越运行,占用的内存越大.我在跑py-faster-rcnn的demo时,基本上跑2000张图像,16g内存就要爆了.于是尝试用python的内存监控工具来调试程序,找到不能膨胀的变量,然后del之,再手动回收内存gc.collec() 下面是我用的两个内存监视工具,一个是按每行代码查看内存占用的工具memory_profil

随机推荐