C++面试:内存篇

关于内存的个人总结:
结构体内存对齐:存储结构体或类对象时编译器会进行一定字节的填充,使其内存对齐,以提高cpu访问的效率。 因为cpu读内存是一块一块读的,而不是逐个字节,如假设cpu一次读4个字节,现在存了一个char和int,要读取int,它存在1-4,得先读0-3,再读4-7,再删除0,5,6,7,最后合并1234,效率太低,若是内存对齐的,他就会存在4-7,一次就读取,相当于空间换时间。计算方法:向最大成员看齐,画图理解。如char a, char b, int a, int b;大小为12
联合体(共同体union):所有成员共用一段内存,内存大小为对齐后最大成员大小,所以改变其中一个成员的值会覆盖内存,进而影响到其他变量。
栈是倒过来的杯子。即形参入栈先占高地址即杯底,然后依次向下,所以栈顶是低地址,这有利于函数调用。访问低地址直接得到局部变量。堆的话主要怕与栈冲突,所以相反。
栈堆区别
1、栈是一块小的连续内存,程序已启动操作系统就会自动分配一个栈,每调用一次函数,就会入队一个栈帧,存放了局部变量、形参等;堆的内存较大,由我们手动申请和释放。
2、栈先进后出,函数执行结束,出队栈帧,这都是系统自动管理的,没有内存碎片;堆的底层是brk系统调用会产生内外碎片(内碎片:分配的内存没使用完;外碎片:堆中有无法使用的小内存块,即2边都被使用了,导致中间这块太小了)。堆的分配方式就是new

栈也能动态分配:int* numbers = (int*)alloca(n * sizeof(int));但也会自动释放。
malloc/new、free/delete:都是用于动态申请内存和释放,malloc/free是c库函数,delete/new是c++关键字;最大区别肯定是否调用构造函数。new的底层=malloc+构造函数,delete底层=析构+free。
malloc原理
1、若申请的内存小于128k,调用brk在堆上分配。若大于128k,调用mmap在文件映射区分配内存。
2、 brk申请内存时系统会分配更大空间,采用内存池管理,free后放回内存池。
linux下执行此程序:

#include <malloc.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
    void* addr = malloc(1);
    printf("此1字节的起始地址: %p\n", addr); //%p:打印地址
    printf("另开一个终端执行cat /proc/%d/maps|grep heap\n", getpid());
    getchar();
    free(addr);
    //printf("释放了1字节的内存,但他其实是回到线程池\n");
    getchar();
}

在这里插入图片描述
执行cat后得到:

559314e24000-559314e45000 rw-p 00000000 00:00 0 [heap]

这段就是malloc(1)实际分配的内存:132k,且在头部多分配了0x2a0个字节(存放内存块的大小等信息)
3、mmap在在文件映射区申请固定大小内存,free后归还系统。
4、为啥要用2种策略呢:brk优点:每次分配内存很快,它只需要把brk指针向上移动即可扩展空间,不用向mmap那样每次都要建立虚拟地址到物理地址的映射,缺点:内外碎片问题(内碎片是因为它是整块分配的如申请1也分配8,外碎片肯定有很小的用不上)。mmap优点无碎片,缺点:每次要建立新的映射,开销很大。所以结合2者,小内存brk,又快也不会有太多碎片,大内存用mmap,避免碎片。
free§只传入了起始地址,他如何知道释放多大内存:向前读取0x2a0字节。
new原理:首先调用 operator new 函数申请空间(底层通过 malloc 实现),后把void地址自动转换成目标类型(malloc返回的就是void),然后调用构造函数进行初始化;delete首先调用析构函数,然后调用 operator delete 释放空间(底层通过 free 实现)。光用malloc/free无法满足动态对象的创建和申请,所以出现了new/delete。
malloc、realloc、calloc的区别:malloc只分配内存,不初始化;calloc还会初始化为0;realloc用于调整已分配内存块的大小
new的种类:普通的new,失败抛出异常;nothrow new:失败返回nullptr;placement new:在已分配的内存上重新构造对象
new[]和delete[]为什么要成对使用:原理都类似。
对于自定义类型来说:new[]动态创建数组,会在头部多申请4字节存放数组长度,delete[]会向前找4个字节得到数组长就知道要析构多少次;若new[],delete:则不会向前找4字节,只会析构一次;若new ,delete[],向前找4个字节越界访问。所以new[]/delete[]要成对使用。但对于内置类型可混用,因为内置类型new/delete直接知道内存大小,直接操作内存即可,也不调用析构。
浅拷贝、深拷贝:只要出现在类里有堆区指针,对对象进行浅拷贝只是拷贝了一个指针,没开辟新地址,2个指针指向同一个地址。深拷贝是重新开辟了一块内存拷贝值,2个堆区指针互不影响。所以浅拷贝消耗小,但有风险:其中一个指针释放内存,那另一个指针就成了悬空指针。
内存泄露:new/malloc申请堆区内存,没有free/delete则该内存在程序运行期间就不能被使用,称为内存泄露。避免方法:成对使用 new/delete,malloc/free;使用智能指针管理内存。
海量数据如何排序:无法一次性加载进内存:肯定是分治,如有n个数据,我们把他分成k份,把每份小数据进行内存排序,得到k个有序小文件,接下来就是合并k个有序文件,如同合并k个有序链表,把每个小文件的第一个元素放入优先级队列或者用小根堆,取出它的根节点,写入缓冲区(缓冲区满了写入磁盘,这样可以减少磁盘IO次数),再把根节点对应的下一个节点放入小根堆,依次重复,直到取出所有数。

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值