文章目录

  • malloc
  • free ( )
  • calloc( )
  • 动态内存分配与变长数组
  • 内存类与动态内存分配

malloc

  • malloc()找到可用内存中一个大小适合的块。
    内存是匿名的;

    也就是说,malloc()分配了内存,但没有为它指定名字。
    然而,它却可以返回那块内存第一个字节的地址
    因此,可以把那个地址赋值给一个指针变量,并使用该指针来访问那块内存。
    因为char代表一个字节,所以传统上曾将malloc()定义为指向char的指针类型

    然而,ANSIC标准使用了一个新类型:指向void的指针。这一类型被用作“通用指针”。
    函数malloc()可用来返回数组指针、结构指针等等,因此一般需要把返回值的类型指派为适当的类型。

    在ANSIC中,为了程序清晰应对指针进行类型指派,但将void 指针值赋值给其他类型的指针并不构成类型冲突。
    如果malloc()找不到所需的空间,它将返回空指针
    我们使用malloc()来创建一个 数组。可以在程序运行时使用malloc()请求一个存储块,另外还需要一个指针来存放该块在内存中的位置。

    例如,如下代码:

double * ptd;
ptd = (double * ) malloc (30 * sizeof(double));
  • 这段代码请求30个double类型值的空间,并且把ptd指向该空间所在位置。

    注意:ptd是作为指向一个double类型值的指针声明的,而不是指向30个double类型值的数据块的指针。

    记住:数组的名字是它第一个元素的地址
    因此,如果令ptd指向一个内存块的第一个元素,就可以像使用数组名一样使用它
    也就是说,可以使用表达式ptd[0]来访问内存块的第一个元素,pd[1]来访问第二个元素,依此类推。
    正如前面所学,可以在指针符号中使用数组名,也可以在数组符号中使用指针。

  • 现在,创建一个数组有三种方法:
    1.声明一个数组,声明时用常量表达式指定数组维数,然后可以用数组名访问数组元素。
    2.声明一个变长数组,声明时用变量表达式指定数组维数,然后用数组名来访问数组元素(这是C99的一个特性)。
    3.声明一个指针,调用malloc(),然后使用该指针来访问数组元素。

使用第二种或第三种方法可以做一些用普通的数组声明做不到的事:

创建一个动态数组(dynamic array),即一个在程序运行时才分配内存并可在程序运行时选择大小的数组。

例如,假定n是一个整数量。在C99之前,不能这样做:

double item[n]:/*如果n是一个变量,C99之前不允许这样做*/

然而,即使在C99之前的编译器中,也可以这样做:

ptd =(double*)malloc(n*sizeof(double));/*可以*/

这行得通,而且正如您将看到的那样,这样做比使用一个变长数组更灵活。

一般地,对应每个malloc()调用,应该调用一次free()。
函数free()的参数是先前malloc()返问的地址,它释放先前分配的内存。

这样,所分配内存的持续时间从调用malloc()分配内存开始,到调用free()释放内存以供再使用为止

  • 设想malloc()和free()管理着一个内存池。
    每次调用malloc()分配内存给程序使用,每次调用free()将内存归还到池中,使内存可被再次使用。

注意:

  1. free()的参数应是一指针,指向由malloc()分配的内存块;
  2. 其他方式(例如声明一个数组)分配的内存是不能使用free()去释放的。

在头文件stdlib.h中有malloc()和free()的原型。(我不知道,哈哈)

通过使用malloc(),程序可以在运行时决定需要多大的数组并创建它。

程序清单12.14 举例证明了这可能。

它把内存块地址赋给指针ptd,接着以使用数组名的方式使用ptd。

程序还调用了exit()函数。该函数的原型在 stdlib.h 中,用来在内存分配失败时结束程序

  • EXIT_FAILURE 也在这个头文件中定义。标准库提供了两个保证能够在所有操作系统下工作的返回值:
    EXIT SUCCESS(或者,等同于0)指示程序正常终止;
    EXIT_FAILURE指示程序异常终止。

    另外,有些操作系统,包括UNIX、Linux 和Windows,能够接受其他的整数值。

程序清单12.14 dyn_arr.c 程序

/*dyn_arr.c -- 为数组动态分配存储空间*/
#include<stdio.h>
#include<stdlib.h>int main(void) {double* ptd;int max;int number;int i = 0;puts ("What is the maximum number of type double entries?" );//entries:entry 输入scanf("%d", &max);//输入个数ptd = (double*)malloc(max * sizeof(double));if (ptd == NULL) {exit(EXIT_FAILURE);}///*ptd现在指向有max个元素的数组*/puts("Enter the values(q to quit):");while (i < max && scanf("%lf", &ptd[i]) == 1)++i;printf("Here are your %d entries:\n", number = i);for(i = 0; i < number; i++){printf("%7.2f", ptd[i]);/*.2表示输出数据保留小数点后2位小数,第3位四舍五入;7表示输出数据在终端设备上占用7个字符宽度右对齐,实际数据位数(包括小数点)小于7时左边用空格补齐,大于7时按实际位数向右扩展输出。*/if(i % 7  == 6)putchar('\n');}if(i % 7 != 0)putchar('\n');puts("Done.");free(ptd);return 0;
}

下面是一个运行示例。该例中输入了6个数,但程序只处理了5个,因为我们将数组大小限定为5。

代码分析:

  1. 程序通过下列几行获取所需的数组大小:
puts ( "What is the maximum number of type double entries?" );
scanf("%d", &max);//输入个数
  1. 接着,下面的行分配对于存放所请求数目的项来说足够大的内存,并将该内存块的地址赋给指针ptd:
ptd = (double*)malloc(max * sizeof(double));
  1. 在C中,类型指派(double*)是可选的,而在C++中必须有,因此使用类型指派将使把C程序移植到C++更容易。

    malloc()可能无法获得所需数量的内存。在那种情形下,函数返回空指针,程序终止。

 if (ptd == NULL) {exit(EXIT_FAILURE);//内存分配失败}

如果成功地分配了内存,程序将把ptd视为一个具有max个元素的数组的名字。

  • 注意
    在程序末尾附近的函数free()。
    它释放malloc()分配的内存。
    函数free()只释放它的参数所指向的内存块。
    在这个特定例子中,使用free()不是必须的,因为在程序终止后所有已分配的内存都将被自动释放。
    然而在一个更复杂的程序中,能够释放并再利用内存将是重要的。

  • 使用动态数组将获得什么?

    主要是获得了程序灵活性
    假定知道一个程序在大多数情况下需要的数组元素不超过100个;
    而在某些情况下,却需要l0000个元素。
    在声明数组时,不得不考虑到最坏情形并声明一个具有10000个元素的数组。
    在多数情况下,程序将浪费内存。
    如果有一次需要10001个元素,程序就会出错。
    您可以使用动态数组来使程序适应不同的情形。


free ( )

在编译程序时,静态变量的数量是固定的:在程序运行时也不改变。

自动变量使用的内存数量在程序执行时自动增加或者减少。
但被分配的内存所使用内存数量只会增加,除非您记得使用free()。

例如,假定有一个如下代码勾勒出的函数,它创建一个数组的临时拷贝:

#include<stdio.h>
#include<malloc.h>void gobble (double ar[], int n);int main(){double glad[2000];int i;for(i = 0; i<100000000; i++){gobble(glad, 2000);}
}void gobble(double ar[], int n){double *temp = (double *) malloc(n*sizeof(double)) ;//    free(temp);/*忘记使用*/
}
  • 第一次调用gobble()时,它创建了指针temp,并使用malloc()为之分配16000字节的内存(设double是8个字节)。
    假定我们如暗示的那样没有使用free()。
    当函数终止时,指针temp作为一个自动变量消失了。
    但它所指向的16000个字节的内存仍旧存在。
    我们无法访问这些内存,因为地址不见了。
    由于没有调用free(),不可以再使用它了。
  • 第二次调用gobble(),它又创建了一个temp,再次使用malloc()分配16000个字节的内存。
    第一个16000字节的块已不可用,因此malloc()不得不再找一个l6000字节的块。
    当函数终止时,这个内存块也无法访问,不可再利用。

但循环执行了1000次,因此在循环最终结束时,已经有1600万字节的内存从内存池中移走。
事实上,在到达这一步前,程序很可能已经内存溢出了。

这类问题被称为内存泄漏(memory leak),可以通过在函数末尾处调用free()防止该问题出现。


calloc( )

内存分配还可以使用calloc()。

典型的应用如下:

long * newmen;
newmen = (long *) calloc (100, sizeof( long));

与 malloc()类似,calloc()在ANSI以前的版本中返回一个 char 指针,在ANSI中返回一个void指针。
如果要存储不同类型,应该使用类型指派运算符
这个新函数接受两个参数,都应是无符号的整数(在ANSI中是size_t类型)。

第一个参数是所需内存单元的数量,第二个参数是每个单元以字节计的大小

在这里,long使用4个字节,因此这一指令建立了100个4字节单元,总共使用400个字节来存储。

使用 sizeof(long)而不是 4 使代码更具可易移植性。
它可在其他系统中运行,这些系统中 long不是4字节而是别的大小。

函数calloc()还有一个特性:
它将块中的全部位都置为0(然而要注意,在某些硬件系统中,浮点值0不是用全部位为0来表示的)。

  • ps:个人觉得它相当于数组的初始化。

函数free()也可以用来释放由calloc()分配的内存。

动态内存分配是很多高级编程技巧的关键。

在17章“高级数据表示”中我们将研究一些。您自己的C库可能提供了其他内存管理函数,有些可移植,有些不可以。您可能应该抽时间看一下。


动态内存分配与变长数组

变长数组(Variable-Length Array,VLA)malloc()在功能上有些一致。例如,它们都可以用来创建一个大小在运行时决定的数组:

int valmal(){int n;int * pi;scanf("%d", &n);pi = (int *) malloc(n*sizeof(int));int ar[n];//变长数组 pi[2] = ar[2] = -5;
}

一个区别在于 VLA 是自动存储的。

自动存储的结果之一就是 VLA 所用内存空间在运行完定义部分之后会自动释放

在本例中,就是函数 vlamal()终止的时候。因此不必使用 free()。

另一方面,使用由 malloc()创建的数组不必局限在一个函数中。

例如,函数可以创建一个数组并返回指针,供调用该函数的函数访问。
接着,后者可以在它结束时调用 free()。
free()可以使用不同于 malloc()指针的指针变量:必须一致的是指针中存储的地址

VLA 对多维数组来说更方便
您可以使用 malloc()来定义一个二维数组,但语法很麻烦。
如果编译器不支持 VLA 特性,必须固定一维的大小,正如下面的函数调用:

 int n = 5;int m = 6;int ar2[n][m];//n*m 的变长数组 int(* p2) [6];//在c99之前可以使用 int(* p3) [m];//要求变长数组支持 p2 = (int (*)6) malloc (n * 6 * sizeof(int));// n*6 数组 p3 = (int (*)[m]) malloc (n * m * sizeof(int));// n*m 数组 //上面的表达式也要求变长数组支持 ar2[1][2] = p2[1][2] = 12;

有必要查看一下指针声明。函数malloc()返回一个指针,因此p2必须是适当类型的指针。
下面的声明:

int(* p2) [6];//在c99之前可以使用

表明p2指向一个包含6个int值的数组
这意味着p2[j| 将被解释为一个由6个整数构成的元素,p2[ i ][ j ]将是一个int值。

第二个指针声明使用变量来指定p3所指数组的大小。
这意味着p3将被看作一个指向 VLA 的指针,这正是代码不能在C90标准中运行的原因。


内存类与动态内存分配

您可能正在为存储类和动态内存分配之间的联系感到疑惑。

  • 我们来看一个理想模型。
    可以认为程序将它的可用内存分成了三个独立的部分:
  • 一个是具有外部链接的、具有内部链接的以及具有空链接的静态变量的:
  • 一个是自动变量的:
  • 另一个是动态分配的内存的。

(静态变量):
在编译时就已经知道了静态存储时期存储类变量所需的内存数量,存储在这一部分的数据在整个程序运行期间都可用

这一类型的每个变量在程序开始时就已存在,到程序结束时终止。

(动态变量):
然而,一个自动变量在程序进入包含该变量定义的代码块时产生,在退出这一代码块时终止。

因此,伴随着程序对函数的调用和终止,自动变量使用的内存数量也在增加和减少。

典型地,将这一部分内存处理为一个堆栈

这意味着在内存中,新变量在创建时按顺序加入,在消亡时按相反顺序移除

(动态内存分配):
动态分配的内存在调用malloc()或相关函数时产生,在调用free()时释放。

由程序员而不是一系列固定的规则控制内存持续时间,因此内存块可在一个函数中创建,而在另一个函数中释放。(malloc 可以跨函数调用)

由于这点,动态内存分配所用的内存部分可能变成碎片状,也就是说,在活动的内存块之间散布着未使用的字节片

不管怎样,使用动态内存往往导致进程比使用堆栈内存慢


本文选自《C primer plus》

动态内存分配(malloc)详解相关推荐

  1. 动态内存分配 (详解版)

    文章目录 malloc和free new和delete 1.使用new动态分配内存 2.使用delete释放动态申请的内存 3.使用new申请动态数组 4.不使用或释放已经释放的内存块 常见的动态内存 ...

  2. 动态内存分配(详解)

    动态内存分配 1.堆内存分配 : C/C++定义了4个内存区间:代码区,全局变量与静态变量区,局部变量区即栈区,动态存储区,即堆(heap)区或自由存储区(free store). 堆的概念: 通常定 ...

  3. 内存分配函数(动态内存分配)详解

    目录

  4. c语言malloc引用类型作参数,c语言中动态内存分配malloc只在堆中分配一片内存.doc...

    c语言中动态内存分配malloc只在堆中分配一片内存 .C语言中动态内存分配(malloc)只在堆中分配一片内存,返回一个void指针(分配失败则返回0),并没有创建一个对象.使用时需要强制转换成恰当 ...

  5. 关于动态内存分配malloc的初级用法和注意事项

    #include <stdio.h> #include <stdlib.h> int main(int argc, const char *argv[]) { /*your c ...

  6. C语言学习笔记10-指针(动态内存分配malloc/calloc、realloc、释放free,可变数组实现;Tips:返回指针的函数使用本地变量有风险!;最后:函数指针)

    C语言:指针 1. 指针:保存地址的变量 *p (pointer) ,这种变量的值是内存的地址.   取地址符& 只用于获取变量(有地址的东西)的地址:scanf函数-取地址符   地址的大小 ...

  7. linux c free大段内存,Linux C 动态内存分配--malloc,new,free及相关内容

    一.malloc()和free()的基本概念以及基本用法: 1.函数原型及说明: void *malloc(long NumBytes):该函数分配了NumBytes个字节,并返回了指向这块内存的指针 ...

  8. C++内存分配方式详解——堆、栈、自由存储区、全局/静态存储区和常量存储区

    栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区.里面的变量通常是局部变量.函数参数等.在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用. 和堆一 ...

  9. C++内存分配方式详解——堆、栈、自由存储区、全局/静态存储区和常量存储区...

          栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区.里面的变量通常是局部变量.函数参数等.在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调 ...

  10. C++——内存分配方式详解

    堆.栈.自由存储区.全局/静态存储区和常量存储区 详见:http://www.cnblogs.com/Azhu/p/4436531.html 明确区分堆与栈 堆与栈的区分问题,似乎是一个永恒的话题,由 ...

最新文章

  1. 低数值精度推理和训练
  2. 投稿2877篇,EMNLP 2019公布4篇最佳论文
  3. 如何使用canvas绘图
  4. 一个不错的android学习网站
  5. 团队作业第一次-团队团队展示
  6. MicroBlaze程序融合fpga的bit并烧写入Flash方法
  7. iphone开热点电脑总是搜不到问题的解决办法(百试百灵)
  8. 我的WCF之旅(10):如何在WCF进行Exception Handling
  9. mysql+cls()_MySQL性能优化之show processlist(一)
  10. 【车间调度】基于matlab遗传算法求解混合流水车间调度最优问题【含Matlab源码 901期】
  11. 白马非马----继承 (转)
  12. 如何让novnc/websockify支持tls1.2 (by quqi99)
  13. python实现百万英雄答题神器
  14. linux之打包压缩(tar,gz,bz2,xz,zip)
  15. python成语接龙到为所欲为_为所欲为是第一个成语开头的成语接龙游戏
  16. 微信小程序——获取步数
  17. 单片机 怎调用显示屏字库_51单片机示波器制作(12864显示带字库)
  18. zendstiduo背景色设置为保护眼睛的颜色》》》
  19. 4款黑科技级别的宝藏APP,能够轻松满足你的多种需求,请低调收藏
  20. pandas——数据移位、数据转换、数据合并、数据导出和日期数据的处理,时间序列等

热门文章

  1. 创意编程-创意自画像
  2. 浅谈孩子们的科技启蒙教育
  3. Android之Layout布局
  4. TTS英文版语音合成(免费),可以用于为paper的视频配音
  5. tensorflow学习笔记(十):GAN生成手写体数字(MNIST)
  6. 华尔街之狼-贝尔福特
  7. linux移植过程注意事项,linux移植过程的错误记录
  8. DNS服务器之二:权威服务器的搭建(正向、反向、双向解析、远程更新、辅助DNS)与动态域名解析
  9. iOS 获取IP地址方法
  10. 设备管理器如何改成锁定计算机,解决方案:为什么不能设置计算机指纹解锁设置?...