面试题:c++去new一个对象需要切换到内核态吗?——拼多多面试

旅行   2024-10-24 08:02   广东  

欢迎关注本公众号,专注面试题拆解

分享一套视频课程:《C++实现百万并发服务器》 面试需要项目的可以找我获取
,免费分享。 欢迎V:fb964919126
网络编程系列,已经更新5篇,欢迎阅读:网络编程







c++去new一个对象需要切换到内核态吗?





要回答这个问题,首先需要对两个概念有所了解,用户态和内核态。


01

用户态VS内核态

在现代操作系统中,CPU 通常有两个主要的执行模式:用户态(user mode)和 内核态(kernel mode)。  


用户态:普通应用程序运行在用户态,不能直接访问硬件资源,也不能直接进行诸如内存管理、I/O 操作等敏感操作。  


内核态:操作系统内核运行在内核态,具有对所有资源的完全控制权,可以直接操作硬件。内核态提供系统调用(system call)接口,允许用户态程序请求内核完成某些任务。

其实本质上就是CPU 指令集权限不同,以 Inter CPU 为例,Inter把 CPU 指令集 操作的权限由高到低划为4级:ring0 ring1 ring2 ring3其中 ring0 权限最高,可以使用所有 CPU 指令集,ring3 权限最低,仅能使用常规 CPU 指令集,不能使用操作硬件资源的 CPU 指令集,比如 I O 读写、网卡访问、申请内存都不行,Linux系统仅采用ring 0 和 ring 3 这2个权限。


当一个应用程序需要执行某些需要特权才能完成的操作(如读写文件、网络通信等)时,它会发起一个系统调用。这个过程通常包括以下几个步骤:

系统调用陷阱(System Call Trap):应用程序通过一个特殊的指令(如Intel x86架构上的int $0x80)发起系统调用。这个指令会导致CPU从用户态切换到内核态。
内核态执行:切换到内核态后,CPU开始执行内核提供的系统调用处理程序。这些处理程序可以执行特权指令,并访问内核数据结构。
执行结果返回:系统调用完成后,处理程序会将结果传递给用户空间的应用程序,并将控制权交还给用户态。


从用户态切换到内核态。这种切换开销相对较大,因为需要进行上下文切换、权限检查等操作。

02

new操作符的工作流程


C++ 中的new操作符用于在堆(heap)上动态分配内存。其工作原理可分为两个步骤:

1. 分配内存:new操作符调用内存分配函数(如 malloc 或 operator new)为对象分配足够的内存。

2. 调用构造函数:内存分配完成后,new操作符会调用对象的构造函数对该内存区域进行初始化。


内存分配是关键步骤,决定了是否会发生用户态到内核态的切换。


一:用户态内存分配

C++ 的内存管理通常依赖于标准库提供的堆管理机制。在大多数情况下,内存管理在用户态中完成。C++ 中常用的分配函数(如 malloc 或底层的 operator new)首先从用户态分配器管理的内存池中获取内存。内存池本质上是从操作系统申请到的虚拟地址空间,操作系统为该进程维护着一个虚拟内存的堆区域,这个区域通常已经预先分配了相当多的内存页。


在这种情况下,内存分配是纯粹的用户态操作,没有发生任何系统调用或用户态到内核态的切换。


二:内存不足时的内核态切换

当用户态的堆内存池不足时(例如进程首次分配大块内存,或已分配的堆区域被用完),内存分配器需要向操作系统请求更多的内存。在 UNIX/Linux 系统中,这种操作通常通过以下系统调用完成:


brk/sbrk:这些系统调用用于调整进程的数据段(包括堆)的边界,通常扩展堆的大小。

mmap:用于在进程地址空间中映射新的内存区域,特别是在分配大块内存时。


这些系统调用需要切换到内核态,因为内核是负责管理进程虚拟地址空间和物理内存的。当调用 brk或 mmap时,内核会修改进程的内存映射表,为其分配新的内存页,确保新分配的内存是可用的。


因此,当堆内存池耗尽并需要扩展时,new操作符会间接引发用户态到内核态的切换。


三:内存分配器优化

为了减少频繁的用户态到内核态切换,许多现代内存分配器(如 tcmalloc、jemalloc等)进行了大量优化:


分配大块内存:分配器一次性向操作系统申请大块内存,避免频繁的系统调用。


内存碎片管理:分配器会合理管理小块内存的分配和回收,减少大块内存的浪费。


多线程优化:一些分配器还针对多线程环境进行了优化,减少线程间竞争带来的开销。


四:代码示例:

#include <iostream>
int main() { // 分配一个小对象 int* smallObj = new int;
// 分配一个大对象 int* largeObj = new int[1000000000]; // 分配大块内存 delete smallObj; delete[] largeObj;
return 0;}

smallObj的分配通常会在用户态内存池中进行,不会触发内核态切换。

largeObj的分配由于内存需求较大,可能会导致用户态到内核态的切换,特别是如果当前堆空间不足时。


03

总结

new操作符本身并不会直接导致用户态到内核态的切换。通常情况下,内存分配是在用户态完成的,尤其是当内存分配器能够从现有的内存池中获取到足够的内存时。然而,当堆内存耗尽或需要更大的内存块时,分配器需要向操作系统请求更多内存,这时会发生内核态的切换。


题外话:送书了,总共送出5本,欢迎阅读这篇文章评论参与:

1024程序员节送书了!读很久都不过时的计算机神作,本本经典!

书单如下:

《C++ Primer 中文版(第5版)》

《C++ Primer习题集(第5版)》

《Essential C++中文版》

《C++标准库(第2版)》

《深度探索C++对象模型》

《剑指Offer:名企面试官精讲典型编程题(第2版)》

《剑指Offer(专项突破版):数据结构与算法名企面试题精讲》

《Effective C++:改善程序与设计的55个具体做法(第3版 中文版)》

《More Effective C++:35个改善编程与设计的有效方法(中文版)》

《C++之美:代码整洁、安全又跑得快的30个要诀》

《C++之旅(第3版)》

《程序员的自我修养—链接、装载与库》

《编码》

end



CppPlayer 



关注,回复【电子书】珍藏CPP电子书资料赠送

精彩文章合集

专题推荐

【专辑】计算机网络真题拆解
【专辑】大厂最新真题
【专辑】C/C++面试真题拆解

CppPlayer
一个专注面试题拆解的公众号
 最新文章