2020校招备战日记4.23 ---- 硬币凑数问题(完全背包),内存映射,写时拷贝,动态分配,堆和栈的区别

文章目录

      • 0.目标完成情况
      • 1. 学习内容复盘
        • 硬币
        • 零钱兑换II
        • 内存映射
          • 再看共享对象
          • Copy On Write
          • 再看fork函数
          • 再看execve函数
        • 动态内存分配
          • 为什么需要动态分配呢?(堆和栈的区别)
      • 2. 明日目标

0.目标完成情况

  • LeetCode两道题
  • C++primer看一点
  • csapp完成虚拟内存后面部分,看不懂就跳过。

1. 学习内容复盘

硬币

零钱兑换II

以上两题基本一模一样,想了好久才搞懂。开始只会用回溯法,总是超时。标准解法是动态规划。

这两题都是求可能的方案总数。我认为其突破口在于两点:

  1. 分类。按照第k个硬币取还是不取,分成两个不重复、不遗漏的类。然后就可以分别讨论,从而有了递归的思路了。
  2. 打表。在打表的过程中,细化想法,进而发现优化思路,快速写出代码。在我看来除非特别熟练,否则动态规划的题一定不能少了打表的过程。

内存映射

这部分内容看得感觉很有收获,解决了一些长久以来的疑惑。

昨天学过虚拟内存的表示,其实就是一些结构体,这些结构体将整个虚拟内存划分成了一个一个区域。现在,所谓内存映射,就是将这些区域与磁盘中的某些位置进行关联,当然也可以关联二进制零。这里的关键是“与磁盘中的位置”进行关联,而不是内存,所以不要混淆了内存映射和地址翻译。

具体如何关联(映射)的,csapp只是介绍了几个用户级的函数,可以实现映射的功能,例如mmap函数和munmap函数,分别实现创建虚拟内存区域并应映射和销毁虚拟内存的功能。

再看共享对象

如果磁盘中的某个对象被作为共享对象,为多个进程引用,那么每个进程的虚拟内存中,都会有与之关联的互相独立的区域,但是!这些独立的虚拟内存区域,最终都映射到了同一个物理内存位置(页帧号),从而每个进程对该区域的操作,对其他进程而言都是可见的,并且,对该对象的写操作会反应到磁盘中。这,就叫做共享内存,共享对象。

Copy On Write

刚才说的是共享对象的概念,而在Linux中,Copy On Write 问题是针对自己独有的私有对象而提出的。当虚拟内存关联一个对象并且希望将其作为私有的时候,他并不会在引用内存的时候着急在物理内存中复制一份自己专属的拷贝。而是只有当其希望对虚拟内存对象进行写的时候,才会单独拷贝一份出来,作为自己私有的对象进行写,而写的结果也不会反映到磁盘中。也就是说,再此之前,虽然逻辑上我们认为进程私有了一个对象,但是其实物理上还是共享的。这就类似于深拷贝和浅拷贝的概念。

再看fork函数

当进程执行fork函数的时候,内核只是进行了一次“浅拷贝”,尽管我们认为子进程是拥有和父进程独立的虚拟地址空间的。父子进程共享物理内存,直到其中一方打算对共享的内存进行写操作,那么该操作会被中断,进入写时拷贝处理程序,也就是先拷贝一份,修改相应页表,然后再回过来进行写操作。这样的“延迟”拷贝可以尽可能的减少拷贝操作,最大限度的增加内存的共享程度,节约物理内存资源。

再看execve函数

该函数通过3个步骤来实现程序的加载和运行:

  1. 删除旧的虚拟内存区域
  2. 创建新的虚拟内存映射、共享库映射
  3. 将程序计数器PC设置成新的代码段起始地址。

由此可见,所谓加载的过程中,并没有实际代码、数据、共享库的“从磁盘到内存的拷贝”,仅仅是一些关联工作, 只有当cpu请求某个虚拟页,并且引起缺页中断的时候,才会真正从磁盘中将相应的页换入以及可能的换出。

动态内存分配

前面一直提到,其实堆就是一个由动态分配器维护的虚拟内存区域。动态分配器也就是一段代码,他是通过维护一些数据结构来维护虚拟内存区域的。动态分配器的底层用到了前面讲过的mmap函数,也就是说,动态分配器也是用来进行虚拟内存的创建、映射、回收的。程序员通常会使用动态分配器而不是mmap函数。

动态分配器主要为程序员提供了两种接口,分配内存和销毁内存。动态分配器的设计根据“是否需要手动释放内存”分为两种风格:

  1. C/C++ 的显式分配器。 需要手动显式的释放已分配的内存。
  2. Java/Lisp 的隐式分配器。不需要手动显式的释放。

对于两种风格的动态分配器,其分配内存的操作都必须是显式的。
隐式分配器为什么不需要手动释放呢?因为这些语言对指针的使用有着很严格的限制,所以分配器可以精确地维护可达图。所谓可达图,就是分配器眼中的堆。

所以Java语言可以对那些已经不可达的内存进行自动回收,称为垃圾回收,而具有垃圾回收功能的动态分配器也成为垃圾收集器(Garbage Collector)。

为什么需要动态分配呢?(堆和栈的区别)
  • 因为在很多时候,我们只有在程序运行的时候才知道程序需要开辟多大的内存。而实现这种“根据运行时状态来分配内存”的重要实现方式,就是使用堆。栈的内存分配和释放,通常在编译成汇编代码之后就已经确定了。
  • 堆是向上增长,其可以使用的空间较大。而栈是向下增长,空间较小。
  • 堆内存的分配和释放都有程序员自己决定,因此可能会发生内存泄露,程序员甚至可以自己实现分配器,来控制内存的分配、分割、合并、回收等操作,而栈则没有这些问题,因为栈的分配和释放是系统自动管理的,不过栈会有栈溢出的问题。

2. 明日目标

  1. LeetCode 两道题
  2. 开始看第10章,系统级IO。最好看一半。
  3. 看视频,linux扫盲视频。

你可能感兴趣的:(C++,备战校招日记,c++)