济宁做网站多少钱,武威 网站建设,杭州最大定制app开发公司,wordpress 后台 模板一、malloc函数
谈到malloc函数相信学过c语言的人都很熟悉#xff0c;但是malloc底层到底做了什么又有多少人知道。
1.1 关于malloc相关的几个函数
可以这样认为#xff08;window下#xff09;原型#xff1a;
extern void *malloc(unsigned int num_bytes);如果分配成…一、malloc函数
谈到malloc函数相信学过c语言的人都很熟悉但是malloc底层到底做了什么又有多少人知道。
1.1 关于malloc相关的几个函数
可以这样认为window下原型
extern void *malloc(unsigned int num_bytes);如果分配成功则返回指向被分配内存空间的指针不然返回指针NULL 。同时当内存不再使用的时候应使用free()函数将内存块释放掉。
关于void*表示未确定类型的指针cc规定void*可以强转为任何其他类型的指针关于void还有一种说法就是其他任何类型都可以直接赋值给它无需进行强转但是反过来不可以 。
malloc:
malloc分配的内存大小至少为参数所指定的字节数malloc的返回值是一个指针指向一段可用内存的起始位置指向一段可用内存的起始地址多次调用malloc所分配的地址不能有重叠部分除非某次malloc所分配的地址被释放掉malloc应该尽快完成内存分配并返回不能使用NP-hard的内存分配算法实现malloc时应同时实现内存大小调整和内存释放函数realloc和free malloc和free是配对的如果申请后不释放就是内存泄露如果无故释放那就是什么也没做释放只能释放一次如果一块空间释放两次或者两次以上会出现错误但是释放空指针例外释放空指针也等于什么也没做所以释放多少次都是可以的。
1.2 malloc和new
new返回指定类型的指针并且可以自动计算所需要的大小。
int *p;
p new int;//返回类型为int* 分配的大小是sizeof(int)
p new int[100];//返回类型是int*类型分配的大小为sizeof(int)*100而malloc需要我们自己计算字节数并且返回的时候要强转成指定类型的指针。
int *p;
p (int *)malloc(sizeof(int));malloc的返回是void*如果我们写成了pmalloc(sizeof(int))间接的说明了将void转化给了int*这不合理malloc的实参是sizeof(int)用于指明一个整型数据需要的大小如果我们写成p(int*)malloc(1),那么可以看出只是申请了一个一个字节大小的空间。malloc只管分配内存并不能对其进行初始化所以得到的一片新内存中其值将是随机的。
一般意义上我们习惯性的将其初始化为NULL当然也可以使用memset函数。
简单的说
malloc函数其实就是在内存中找一片指定大小的空间然后将这个空间的首地址给一个指针变量这里的指针变量可以是一个单独的指针也可以是一个数组的首地址这要看malloc函数中参数size的具体内容。我们这里malloc分配的内存空间在逻辑上是连续的而在物理上可以不连续。我们作为程序员关注的是逻辑上的连续其他的操作系统会帮着我们处理。
1.3 malloc实现原理
虚拟内存地址和物理内存地址
为了简单现代操作系统在处理物理内存地址时普遍采用虚拟内存地址技术。即在汇编程序层面当涉及内存地址时都是使用的虚拟内存地址。采用这种技术时每个进程仿佛自己独享一片2N字节的内存其中N是机器位数。例如在64位CPU和64位操作系统下每个进程的虚拟地址空间为264Byte。
这种虚拟地址空间的作用主要是简化程序的编写及方便操作系统对进程间内存的隔离管理真实中的进程不太可能如此大的空间实际能用到的空间大小取决于物理内存的大小。 由于在机器语言层面都是采用虚拟地址当实际的机器码程序涉及到内存操作时需要根据当前进程运行的实际上下文将虚拟地址转化为物理内存地址才能实现对内存数据的操作。这个转换一般由一个叫MMU的硬件完成。
页与地址构成
在现代操作系统中不论是虚拟内存还是物理内存都不是以字节为单位进行管理的而是以页为单位。一个内存页是一段固定大小的连续的连续内存地址的总称具体到Linux中典型的内存页大小为4096 Byte。所以内存地址可以分为页号和页内偏移量。下面以64位机器4G物理内存4K页大小为例虚拟内存地址和物理内存地址的组成如下 上面是虚拟内存地址下面是物理内存地址。由于页大小都是4k所以页内偏移都是用低12位表示而剩下的高地址表示页号 MMU映射单位并不是字节而是页这个映射通过差一个常驻内存的数据结构页表来实现。现在计算机具体的内存地址映射比较复杂为了加快速度会引入一系列缓存和优化例如TLB等机制下面给出一个经过简化的内存地址翻译示意图 内存页与磁盘页
我们知道一般将内存看做磁盘的缓存有时MMU在工作时会发现页表表名某个内存页不在物理内存页不在物理内存中此时会触发一个缺页异常此时系统会到磁盘中相应的地方将磁盘页载入到内存中然后重新执行由于缺页而失败的机器指令。关于这部分因为可以看做对malloc实现是透明的所以不再详述
真实地址翻译流程 内存排布明白了虚拟内存和物理内存的关系及相关的映射机制下面看一下具体在一个进程内是如何排布内存的。 以Linux 64位系统为例。理论上64bit内存地址空间为0x0000000000000000-0xFFFFFFFFFFFFFFF这是个相当庞大的空间Linux实际上只用了其中一小部分。
具体分布如图所示 对用户来说主要关心的是User Space。将User Space放大后可以看到里面主要分成如下几段
Code这是整个用户空间的最低地址部分存放的是指令也就是程序所编译成的可执行机器码 Data这里存放的是初始化过的全局变量BSS这里存放的是未初始化的全局变量Heap堆这是我们本文主要关注的地方堆自底向上由低地址向高地址增长Mapping Area这里是与mmap系统调用相关的区域。大多数实际的malloc实现会考虑通过mmap分配较大块的内存空间本文不考虑这种情况这个区域由高地址像低地址增长 Stack:栈区域自高地址像低地址增长 。Heap内存模型一般来说malloc所申请的内存主要从Heap区域分配来看看Heap的结构是怎样的。 Linux维护一个break指针这个指针执行堆空间的某个地址从堆开始到break之间的地址空间为映射好的可以供进程访问而从break往上是未映射的地址空间如果访问这段空间则程序会报错。
1.4 brk与sbrk
由上文知道要增加一个进程实际上的可用堆大小就需要将break指针向高地址移动。Linux通过brk和sbrk系统调用操作break指针。两个系统调用的原型如下
int brk(void *addr);
void *sbrk(inptr_t increment);brk将break指针直接设置为某个地址而sbrk将break从当前位置移动increment所指定的增量。brk在执行成功时返回0否则返回-1并设置为errno为ENOMEMsbrk成功时返回break移动之前所指向的地址否则返回void-1*;
资源限制和rlimirt
系统为每一个进程所分配的资源不是无限的包括可映射的空间因此每个进程有一个rlimit表示当前进程可用的资源上限这个限制可以通过getrlimit系统调用得到下面代码获取当前进程虚拟内存空间的rlimit 其中rlimt是一个结构体
struct rlimit
{rlimt_t rlim_cur;rlim_t rlim_max;
};每种资源有硬限制和软限制并且可以通过setrlimit对rlimit进行有条件限制作为软限制的上限非特权进程只能设置软限制且不能超过硬限制
二、calloc函数
calloc函数也是与free()函数配套使用的使用方式与malloc几乎相同,也是在堆区申请动态内存空间。头文件stdlib.h,返回类型为空指针size_t num为元素个数size_t size为每个元素的字节大小。
calloc函数的原型
void* calloc(size_t num ,size_t size)2.1 calloc函数的使用
#includestdio.h
#includestdlib.h
#includestring.h
#includeerrno.h
int main()
{//calloc与malloc的区别//1.参数的使用方式不同//2.calloc会在返回起始地址之前把在堆区申请的动态内存空间的每个字节都初始化为0int* p(int*)calloc(10, sizeof(int));if (p NULL){printf(%s\n, strerror(errno));}else{int i;for (i 0; i 10; i){printf(%d , *(p i));//0 0 0 0 0 0 0 0 0 0}}//注意要释放calloc申请的那块空间//还给操作系统并把指针置为空free(p);p NULL;return 0;
}2.2 calloc与malloc的区别
1.参数的使用方式不同
malloc(单位字节)malloc(10 * sizeof(int));或malloc(40)
calloc:calloc(10 , sizeof(int))2.malloc的使用效率较高因为calloc在返回在堆区申请的那块动态内存的起始地址之前会将每个字节都初始化为0。
三、realloc函数
3.1 什么是realloc
realloc()是C库的功能用于为已分配的内存块增加更多的内存大小。C语言中重新分配的目的是扩展当前的存储块同时保留原始内容。realloc函数有助于通过malloc或calloc函数减少先前分配的内存大小。realloc代表内存的重新分配。
在C中realloc的语法
ptr realloc (ptr,newsize);上面的语句在变量newsize中分配具有指定大小的新内存空间。执行完函数后指针将返回到存储块的第一个字节。新的大小可以大于或小于以前的内存。我们不能确定新分配的块是否将指向与先前存储块相同的位置。
C语言中的realloc函数将在新区域中复制所有先前的数据。它确保数据将保持安全。
例如
#includeint main () {char *ptr;ptr (char *) malloc(10);strcpy(ptr, Programming);printf( %s, Address %un, ptr, ptr);ptr (char *) realloc(ptr, 20); //ptr is reallocated with new sizestrcat(ptr, In C);printf( %s, Address %un, ptr, ptr);free(ptr);return 0;
} 3.2 如何使用realloc
下面的C语言程序演示了如何在C语言中使用realloc来重新分配内存。
#include stdio.h
#include stdlib.hint main() {int i, * ptr, sum 0;ptr malloc(100);if (ptr NULL) {printf(Error! memory not allocated.);exit(0);}ptr realloc(ptr,500);if(ptr ! NULL)printf(Memory created successfullyn);return 0;}C示例中的realloc结果
Memory created successfully每当重新分配导致操作失败时它都会返回空指针并且先前的数据也将被释放。
1、函数原型void *realloc(void *ptr,size_f size);ptr是指向需要修改的内存块的指针size是请求修改的大小2、realloc用来修改已分配的内存块的大小3、如果调整成功返回值是调整大小后内存的起始位置如果失败则返回NULL所以要对返回值进行判空。4、在扩大内存空间时会出现的两种情况
1ptr所指向的内存后有足够的内存空间用来扩展 2ptr所指向的内存后没有足够的内存空间用来扩展
则在堆上重新找一个大小合适的连续空间来使用这样函数返回的是一个新的内存地址。如果新的内存空间申请成功则会将ptr所指向的内存中的内容拷贝到新的内存空间中ptr所指向的内存会被释放返回新的内存地址如果不成功ptr所指向的内存不会被释放函数返回NULL。 5、p realloc(ptr,size)函数返回值不为空时释放内存不需要写free(ptr),只需要写free(p)6、申请的内存空间不会进行初始化
四、free函数
1、用来释放动态开辟的内存2、函数原型void free(void *ptr);指针参数是指向malloc等函数动态申请的内存地址这块内存释放后会返还给堆虽然指针指向这块区域但可以重新分配这块数据3、一般来说free释放的是最新开辟的一个内存空间如果程序中malloc了但是没有free则会造成内存泄漏即在程序运行过程中系统会一直被申请内存造成可用内存不断减少4、在free之后需要将ptr再次置空即ptr NULL;如果不置空后面程序如果通过ptr会再次访问到已经释放/无效的/已经被回收再利用的内存。5、free不能同时释放一块内存。
在使用以上函数时需要加头文件#include stdlib.h
将free与malloc函数的联用情况
#include stdio.h
#include stdlib.h
#include errno.h
#include string.h
int main()
{int* p (int*)malloc(40);int* ptr p;if (ptr NULL){printf(%s\n, strerror(errno));return 1;}//使用//自行添加使用的代码//释放free(p); p NULL;
}在这个代码中就是将malloc函数与free函数初步联用所以才能更合理的分配内存
但是在malloc函数与free函数联用的情况由于代码的不规范也会出现或多或少的错误
#include stdio.h
#include stdlib.h
#include errno.h
#include string.h
int test ()
{int* p (int*)malloc(40);if (p NULL){printf(%s\n, strerror(errno));return 1;}//使用if (1){//某个成立的条件return 2;}//释放free(p);p NULL;
}//该段代码存在内存泄露的问题
int main()
{test();return 0;其实在该段代码中可能出现内存泄漏的问题
原因在于在该段代码中
//使用if (1){//某个成立的条件return 2;}NULL) { printf(“%s\n”, strerror(errno)); return 1; }
//使用 if (1) { //某个成立的条件 return 2; } //释放 free§; p NULL; }//该段代码存在内存泄露的问题 int main() { test(); return 0; 其实在该段代码中可能出现内存泄漏的问题原因在于在该段代码中
//使用 if (1) { //某个成立的条件 return 2; } 如果条件成立直接返回该值但并不会继续执行代码导致后续的释放//释放 free(p); p NULL;出现问题