C++开发(软件开发)常见面试题
目录
1、C++里指针和数组的区别
2、C++中空指针请使用nullptr不要使用NULL
3、http/https区别和头部结构?
4、有了mac地址为什么还要ip地址?ip地址的作用
5、有了路由器为什么还要交换机?
6、面向对象三大特性
7、友元函数
8、大端小端
9、野指针
10、static
11、指针*和引用&的区别,用sizeof时有什么不同,为什么需要引用?
12、静态变量什么时候初始化?
13、new和malloc区别?原理
14、内存泄漏、如果出现怎么排查和定位?
15、const以及函数后加const?
16、操作系统里堆和栈的区别
17、基类和派生类中构造函数和析构函数的顺序
18、strcpy和memcpy的区别,谁的性能好?
19、malloc和new的区别
20、静态多态和动态多态
21、内存分区
22、文件编译过程
23、虚拟内存
24、浏览器输入网址后执行的过程?
1、C++里指针和数组的区别
先来看一个经典问题char s1[]="hello";
char *s2 ="hello";
1、s1的值是放在栈上的,值是可以修改的,而hello是一个字符串常量放在静态存储区是不能修改的。
2、内存大小不一样
#include<stdio.h>int main(){char s1[]="hello";char *s2="hello";puts(s1);puts(s2);printf(""%ld %ld\n",sizeof(s1),sizeof(s2));return 0;
}
3、无法返回局部变量的地址,栈上的值随着函数调用结束,内存已经回收了
上面这种编译器会报警,下面则不会
2、C++中空指针请使用nullptr不要使用NULL
1. C++中NULL定义就是整数字面量0
2. 对于C++函数,由于存在重载,使用NULL而不是nullptr可能导致函数走错重载。
3. C中定义NULL为(void* )0,确实是代表空指针。使用时隐式转换成对应的需要类型的空指针。
4. C++中void指针不能隐式转换成其他指针,所以无法按照C那样定义。
5. C++中保留NULL可以兼容一些C style的代码,对于这些库,不会使用到函数重载,不会产生对应的问题。但对于纯C++程序,请使用nullptr表示空指针。
3、http/https区别和头部结构?
http超文本传输协议,被用于在web浏览器和网站服务器之间传递消息,以明文的方式发送内容,不提供任何方式的数据加密,传输端口为80,特点是简单快捷,灵活,无状态,每次请求都是独立的,上一次请求和下一次请求互不相干
比如登录某个网站后,本来不需要再登陆,不知道上次请求已经登陆过了
https:http+ssl/tls 基于tls/ssl协议加密进行,引入了会话保持,session和cookie,状态记录-登录验证
TCP传输,拿到的是密文,传输端口为443
Ssl/tls协议依靠证书来验证服务器的身份。
4、有了mac地址为什么还要ip地址?ip地址的作用
IP(包裹地址):一个为互联网的每一个网络和每一台主机分配的逻辑地址
Mac(收件人信息):媒体访问控制地址,局域网地址,以太网地址,物理地址,是一个用来确认网上设备位置的地址
Mac地址用于标示一个网卡,一台设备若有一个或多个网卡,则每个网卡都需要并会有一个唯一的Mac地址,表明身份
只有Mac地址可以传输数据,只要同处于一个局域网内
ISP:互联网服务提供商。
5、有了路由器为什么还要交换机?
路由器的作用是与外部通讯
交换机是提供内网通讯
不是每个网络需要路由器,比如企业,学习,医院等,内部通信需要大量的接入设备,只需要交换机,一台交换机可以接入几十台设备,而仅需一个路由器提供对外访问,更大型的网络里,需要对内部网进行划分若干小内网,实现内部小网之间互相访问,可采用带路由功能的交换机,即三层交换机
路由器侧重点是共享,交换机功能不强,侧重点是多口,构成局域网
如果有50台computer,不得不用交换机,小路由器便宜,但代理能力更弱,只能代理几台,若加很多口,又没有能力代理,没用,但口多代理强的路由器很贵如只要交换机,加了路由,价也贵了,功能也多余的.
6、面向对象三大特性
(1)封装性:将客观事物抽象成类,每个类对自身的数据和方法实行protection
(2)继承性:广义的继承有三种实现形式:实现继承(使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。
(3)多态性:是将父类对象设置成为和一个或更多它的子对象相等的技术。用子类对象给父类对象赋值之后,父类对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
封装是实现面向对象程序设计的第一步,将数据或函数等集合在一个个的单元(类)中。封装的意义就是保护或防止数据被无意破坏。
继承主要用来实现重用代码,节省开发时间,子类可以继承父类。
多态是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针来调用实现派生类中的方法。
继承是面向对象编程中实现代码复用的重要手段。通过继承,子类可以继承父类的属性和方法,并可以添加或覆盖父类的方法。这使得子类能够共享父类的代码,并扩展或修改其功能。
public继承 | protected继承 | private继承 | |
基类的public | 派生出public | 派生出protected | 派生出private |
基类的protected | 派生出protected | 派生出protected | 派生出private |
基类的private | 在派生类不可见 | 在派生类不可见 | 在派生类不可见 |
private基类无论以什么方式继承到派生类中都是不可见的
使用class时默认是private,使用struct默认是public
封装是面向对象编程的核心概念之一,它通过将数据和操作数据的方法绑定到一个对象中,隐藏对象的内部状态和实现细节,只对外提供公共接口。这样可以提高代码的安全性和可维护性。
封装:将类的实现细节隐藏,暴露一个简洁。清晰的接口,提高代码的可重用性、实用性、耦合度、可维护性
访问控制权限:
一个类的public变量、函数可以通过类的实例变量访问,
protected无法通过变量访问,但可以通过类的友元函数、友元类访问
private同protected
多态:多种形态去完成某个行为,当不同对象去完成时会产生不同的状态
买票:学生半价、普通人全价
实现:重写/覆盖:子类有一个跟父类完全相同的虚函数,子类的虚函数重写了基类的虚函数
子类父类都要这个虚函数
多态是面向对象编程中实现接口统一和灵活性的关键特性。通过多态,不同的对象可以对同一消息作出不同的响应。这使程序在运行时能够根据对象的实际类型来执行相应的操作,提高了代码的灵活性和可扩展性。分为静态和动态两种
静态(编译时):重载、泛型编程、重载运算符、模板,编译时自动绑定好
动态(运行时):虚函数、virtual、回调函数
虚函数有一个虚函数表,虚函数表指针一般有四个字节,虚表指针在内存哪个分区取决于在堆上创建还是栈上
虚函数和纯虚函数,通过使用指向基类对象的指针或引用调用虚函数
多态分为两种,一种是编译时多态,比如函数重载和模板,编译器在编译代码时会根据不同的代码匹配相应的函数。另一种是运行时多态,主要通过虚函数实现。在定义类时,定义一个虚函数,当在子类中使用时,只需要重写这个虚函数。
重写虚函数:一般先写函数的返回值、函数名和形参,然后在形参后面加上override关键字。如果是纯虚函数,必须重写才能使用。如果不是纯虚函数,override关键字可以不加。
C++在运行时,虚函数通过虚表(vtable)实现。当一个对象调用虚函数时,程序会通过该对象的虚表指针找到相应的函数地址并进行调用。
虚函数重写使用override关键字重写虚函数。纯虚函数必须重写,非纯虚函数可以不加override。
7、友元函数
友元函数是 一个特性,它允许一个或多个非成员函数访问一个类的私有和保护成员。友元函数不是类的成员函数,但它可以访问类的所有成员,包括私有成员。这种机制提供了一种突破数据封装和隐藏的方式,使得非成员函数能够直接操作类的内部数据。然而,使用友元函数需要谨慎,因为它可能会破坏封装性并增加代码的复杂性。
8、大端小端
大端小端是计算机存储数据的一种字节序方式。
大端模式(Big-Endian)是指高位字节存储在内存的低地址处,而低位字节存储在内存的高地址处;
小端模式(Little-Endian)则相反,低位字节存储在内存的低地址处,高位字节存储在内存的高地址处。这两种模式在跨平台编程和网络通信中需要特别注意。
9、野指针
野指针是指指向已经被释放的内存空间的指针,或者是一个未被初始化的指针。使用野指针可能会导致程序崩溃或数据损坏。为了避免野指针问题,需要在使用指针前进行初始化,并在释放内存后将指针置为nullptr。
10、static
修饰静态局部变量,会改变局部变量的存储位置,从而使得局部变量的生命周期变长,延长至程序结束才销毁,普通的局部变量创建后是放在栈区,这种局部变量进入作用域时创建,出了作用域就销毁。static修饰局部变量只改变生命周期,不改变作用域
修饰全局变量,会改变全局变量的链接属性,使全局变量无法被其他文件调用,作用域变小
全局变量:程序所有源文件,对象及函数都可以调用,生命周期贯穿整个程序,想被另一个文件使用时需要进行外部声明extern
修饰函数:改变函数链接属性,从而使作用域变小,函数本身有外部链接属性,被修饰后成了内部链接属性
11、指针*和引用&的区别,用sizeof时有什么不同,为什么需要引用?
指针:内存地址,指针变量是用来存放内存地址的变量
引用:给已存在的变量取一个别名,编译器不会给引用变量开辟内存空间,共用一块内存空间,主要作用是修饰函数的形参和返回值
在C++中,函数和返回值的传递方式有三种:值传递、指针传递、引用传递,引用具有指针的效率,有=又具有变量使用的方便性
区别:
引用给予程序元素完成其功能的最小权限,指针能无约束地操作内存中的任何东西,非常危险
引用定义必须初始化,指针没有,但尽量初始化,防止野指针
引用在初始化后不能再引用其他实体,指针可以
没有null引用,但是有nullptr指针
在sizeof中含义不同,引用结果为引用类型大小,但指针是地址空间所占字节个数(32位平台占4个字节)
有多级指针,但没有多级引用
12、静态变量什么时候初始化?
初始化只有一次,但可以多次复制,在主程序之前编译器已经为其分配好了内存
静态变量和全局变量一样,都存放在全局区域(数据区)
如果是c:int a=1;int b=a; 是错误的,在编译时才初始化
C++:int a=1;static int b=a;
由于C++引入对象后要进行初始化必须执行相应的构造函数和析构函数,经常会进行特定操作,所以C++为全局或静态对象时有首次用到时才会进行构造,C++中内置类型比如int、double、char都升级成了类,在对象第一次使用时初始化.
13、new和malloc区别?原理
new、delete是关键字,需要编译器支持,
malloc、free是库函数,需要引入相应头文件
malloc:申请空间时要填入申请内存大小,int *m=(int*)malloc(4);堆空间
new:根据类型分配内存 int *a=new int(0); 自由存储区
c++内存分为:堆、栈、自由存储区、全局/静态存储区、常量存储区
堆是操作系统维护的一块特殊内存,提供了动态分配功能,当运行程序调用malloc时会从中分配,调用free归还内存
自由存储区是c++中动态分配和释放对象的概念
通过new分配的内存区域可以称为自由存储区,自由存储区可以是堆、全局/静态存储区等
new返回对象类型的指针,类型与对象匹配
malloc内存分配成功返回void *,需要通过强制类型转换将void*转换成所需,分配失败返回null
new失败会抛出异常,
malloc可以通过realloc扩张,new没有
(1)new是C++中的操作符,malloc是C中的一个函数。
(2)new不止分配内存,而且会调用类的构造函数,同理delete调用类的析构函数,而malloc只分配内存,不会进行初始化类成员的工作,同样free也不会调用析构函数。
(3)内存泄露对于malloc或者new都可以检查出来,区别在于new可以指明是文件的哪一行,而malloc没有这些信息。
(4)new可以认为是malloc加构造函数的执行,new出来的指针是直接带类型信息的,而malloc返回的值都是void指针
14、内存泄漏、如果出现怎么排查和定位?
分配的内存没有被正确释放,导致内存被占用过多,主要与动态内存分配有关,
int *p=new int;
delete p;
对同一个指针重新分配内存:int *p=new int;
p=new int;
导致程序运行效率下降、程序出现安全漏洞、内存资源枯竭
15、const以及函数后加const?
const int a 常整型数
int const a 常整型数
const int *a 指向常整型数的指针,即a的值可以变,*a不可变
int* const a 常指针,a的值不能变,*a可变
int const *a const 指向常整型数的常指针
const修饰函数参数:只能修饰输入作用的参数,如果输入参数为指针,加上const起到保护指针意外修改的作用,
const int fun();没有必要,只是个临时值,最终这个值会复制给接受它的变量
const修饰成员函数:为了保护成员变量,要求const函数不能修改成员变量,否则编译报错
const对象只能访问const成员函数,非const对象可以访问任何成员函数,包括const成员函数
const成员函数可以分为所有成员变量,但只能访问const的成员函数
const成员函数不能修改任何成员变量,除非变量用mutable修饰
在const成员函数中成员变量都变成const属性,无法再次修改
16、操作系统里堆和栈的区别
堆:由程序员分配释放,若不释放,程序结束时可能由OS回收,堆的内部地址生长方向与栈相反,由低到高
栈:由操作系统自动分配释放,存放函数的参数值,局部变量,栈的内部地址是由高到低分配的,因此后定义的变量在栈中的地址低于先定义的变量
空间大小:栈的值是固定的,由编译器不同而不同,一般为2M,较小堆理论上可以分配虚拟内存大小的空间,堆区的内存空间是由链表组织的,是不连续的
17、基类和派生类中构造函数和析构函数的顺序
构造:先基类,再派生类
析构:先派生类,再基类
多个基类的调用跟基类继承的顺序有关
18、strcpy和memcpy的区别,谁的性能好?
strcpy和memcpy都是C语言中常用的字符串复制函数
主要区别
复制内容:
strcpy:只能复制以'\0'结尾的字符串。
memcpy:可以复制任意内容,包括字符数组、整型、结构体、类等,只要指定了正确的长度。
复制方法:
strcpy:不需要指定长度,遇到字符串结束符'\0'时停止复制。如果目标空间不够大,可能会导致缓冲区溢出。
memcpy:根据其第三个参数(即要复制的字节数)来决定复制的长度,不会自动停止。因此,需要确保目标空间足够大以容纳要复制的数据。
参数类型:
strcpy:参数是字符指针(char*)。
memcpy:参数是void指针(void*),提供了更大的灵活性,可以复制任何类型的数据。
安全性:
strcpy:由于不检查目标空间是否足够大,可能会导致缓冲区溢出,存在安全隐患。
memcpy:在正确使用时(即确保目标空间足够大),相对更安全。但如果不小心指定了错误的长度,也可能导致内存问题。
性能表现
对于较短的字符串,strcpy可能需要额外的处理来查找字符串结束符'\0',这可能会使其比memcpy稍微慢一些。然而,这种差异通常非常小,并且在大多数情况下可以忽略不计。
对于较长的字符串或需要复制非字符串数据时,memcpy的性能可能会更好,因为它不需要处理字符串结束符,并且可以直接根据指定的长度进行复制。
在实际应用中,性能的差异还受到编译器优化、内存布局、CPU缓存等多种因素的影响。因此,很难给出一个绝对的结论说哪个函数性能更好。通常,选择哪个函数应该基于具体的使用场景和需求。
总结
如果需要复制以'\0'结尾的字符串,并且目标空间足够大以容纳整个字符串(包括结束符),则可以使用strcpy。但需要注意避免缓冲区溢出的问题。
如果需要复制任意类型的数据或指定长度的字符数组,并且希望避免字符串结束符的处理,则可以使用memcpy。但需要确保目标空间足够大以容纳要复制的数据。
19、malloc和new的区别
malloc函数用于在堆上分配指定字节数的内存,并返回一个指向该内存的指针(类型为void*,通常需要强制类型转换)。
初始化:malloc分配的内存是未初始化的,即内存中的值是未定义的。
释放:使用malloc分配的内存必须使用free函数来释放,否则会导致内存泄漏。
灵活性:malloc只负责分配内存,不涉及对象的构造。因此,对于类类型的对象,仅使用malloc是不足够的,还需要手动调用构造函数。
new运算符是C++中特有的。它用于在堆上分配内存并构造对象。new运算符返回一个指向新创建对象的指针。
初始化:使用new分配并构造的对象会被自动初始化。对于内置类型,将调用默认构造函数(如果适用)或进行零初始化;对于类类型,将调用其构造函数。
释放:使用new分配并构造的对象必须使用delete运算符来释放和销毁。delete运算符会首先调用对象的析构函数,然后释放内存。
便利性:new运算符结合了内存分配和对象构造两个步骤,使代码更加简洁和易于管理。
malloc和new都用于在堆上动态分配内存,但new还负责对象的构造。
malloc返回的是void*类型的指针,需要手动进行类型转换;而new返回的是具体类型的指针。
使用malloc分配的内存必须使用free释放;而使用new分配并构造的对象必须使用delete释放和销毁。
new运算符在分配内存时会进行初始化(对于类类型会调用构造函数),而malloc不会。
在C++中,推荐使用new和delete进行动态内存管理,因为它们更符合C++的面向对象特性。然而,在处理某些与C语言接口的代码或需要精确控制内存布局的场景中,malloc和free仍然是有用的。
20、静态多态和动态多态
静态多态,又称编译期多态,是指在编译时就能确定对象的类型和方法调用的多态性。
实现方式:
函数重载和运算符重载。在C++中,函数重载是指在同一个作用域内,可以声明多个具有相同名字但参数列表不同的函数。运算符重载则是对已有的运算符进行重新定义,使其能够用于用户自定义的类型。
特点:
效率较高:由于静态多态在编译时确定,编译器可以进行优化,提高程序运行效率。
适配性和松耦合性:通过模板和特化等技术,静态多态可以处理不同类型的数据,实现代码的复用和扩展。
泛型设计:静态多态为C++带来了泛型设计的概念
二、动态多态是指在运行时才能确定对象的类型和方法调用的多态性。
实现方式:
继承和虚函数来实现。一个基类中的成员函数可以被声明为虚函数,这意味着该函数在派生类中可以被重写(Override)。当使用基类指针或引用来调用虚函数时,程序会在运行时根据实际对象的类型来确定调用哪个函数。
特点:
灵活性高:动态多态允许程序在运行时根据对象的实际类型来选择合适的方法,提高了程序的灵活性和可扩展性。
接口与实现分离:通过虚函数和继承,动态多态实现了接口与实现的分离,使得代码更加清晰和易于维护。
处理异质对象集合:动态多态可以处理同一继承体系下的异质对象集合,实现多态性。
示例:
在C++中,可以通过继承和虚函数实现动态多态。例如,一个基类中包含一个虚函数,派生类重写该虚函数。当使用基类指针指向派生类对象并调用虚函数时,程序会调用派生类中的重写函数。
三、静态多态与动态多态的比较
本质区别:
静态多态在编译时确定对象的类型和方法调用,由模板具现完成。
动态多态在运行时确定对象的类型和方法调用,由继承和虚函数实现。
接口形式:
静态多态的接口是隐式的,以有效表达式为中心,多态通过模板具现在编译期完成。
动态多态的接口是显式的,以函数签名为中心,多态通过虚函数在运行期实现。
优缺点:
静态多态的优点包括效率高、适配性强、支持泛型设计等;缺点包括调试困难、编译耗时、代码膨胀等。
动态多态的优点包括灵活性高、接口与实现分离、处理异质对象集合等;缺点包括运行期绑定导致一定的运行时开销、编译器无法对虚函数进行优化等。
综上所述,静态多态和动态多态各有优缺点,应根据具体的应用场景和需求来选择合适的多态实现方式。
21、内存分区
在C语言中,程序的内存布局通常被划分为几个不同的区域,每个区域都有其特定的用途和管理方式。以下是对C语言内存分区的详细解释:
代码区(Text Segment):
代码区也被称为文本段或代码段。
这个区域存储了程序的机器指令,即CPU要执行的代码。
代码区通常是只读的,以防止程序意外地修改自己的指令。
当程序被加载到内存中时,代码区的内容从可执行文件中复制而来。
全局/静态数据区(Data Segment):
全局/静态数据区包含了全局变量、静态变量和常量数据。
全局变量和静态变量在程序的整个生命周期内都存在,而常量数据则用于存储字符串常量等不变的值。
这个区域在程序开始执行前就被初始化,并在程序结束时被释放。
它分为已初始化和未初始化两个部分:
已初始化数据区(Data Segment):存储已初始化的全局变量和静态变量。
未初始化数据区(BSS Segment):存储未初始化的全局变量和静态变量,这些变量在程序开始执行前被自动初始化为零。
堆区(Heap):
堆区用于动态内存分配。
程序员可以使用如malloc、calloc、realloc等函数在堆上分配内存,并使用free函数释放内存。
堆区的大小在程序运行时是可变的,由程序员控制。
如果程序员忘记释放已分配的内存,可能会导致内存泄漏。
栈区(Stack):
栈区用于存储局部变量和函数调用信息(如函数参数、返回值地址、局部变量指针等)。
栈的大小在程序编译时确定,并在程序运行时由系统自动管理。
每当函数被调用时,系统会在栈上为该函数的局部变量和调用信息分配空间;当函数返回时,这些空间会被自动释放。
栈的访问速度非常快,因为栈内存通常是连续分配的。
这些内存分区共同构成了C语言程序的内存布局。程序员需要了解这些分区的特性和用途,以便正确地管理内存和编写高效的C语言程序。同时,也要注意避免内存泄漏、缓冲区溢出等常见的内存管理错误。
22、文件编译过程
文件编译的过程是将源代码转换为可执行文件的过程,这个过程通常包括预处理、编译、汇编和链接四个阶段。
一、预处理(Preprocessing)
主要任务是对源代码进行初步的处理,为后续的编译阶段做准备。
处理头文件:通过#include指令,将所需的头文件内容插入到源文件中,形成一个整体的源代码文件。头文件通常包含函数声明、宏定义、类型定义等。
宏替换:将所有定义的宏进行替换,例如将所有出现的宏名替换为对应的宏定义内容。宏定义通常使用#define指令进行。
条件编译:根据预处理指令(如#ifdef、#ifndef、#if、#elif等)的条件判断,选择性地编译代码段。
删除注释:删除所有的注释内容,包括单行注释(//)和多行注释(/* */)。
预处理后的结果通常是一个中间文件,这个文件包含了经过宏替换、条件编译等处理后的源代码。
二、编译(Compilation)
编译是编译过程中的核心阶段,主要任务是将预处理后的源代码转换为汇编语言。
词法分析:将源代码划分为一个个的标记(token),如关键字、标识符、运算符等。
语法分析:根据语法规则,将标记组合成语法树,并检查代码是否符合语法规范。
语义分析:对语法树进行语义检查,包括类型检查、变量声明检查等。如果源代码有语法或语义错误,编译器会报错并停止编译过程。
编译后的结果通常是汇编代码,这些代码包含了程序的基本逻辑和运算指令。
三、汇编(Assembly)
汇编是将编译生成的汇编代码转换为机器指令的过程。
符号解析:将变量和函数引用与其定义进行关联,生成符号表。
生成机器码:将汇编指令翻译成机器指令,并生成目标文件(通常是.o或.obj文件)。目标文件包含了程序的可执行代码以及相关的调试信息。
四、链接(Linking)
链接是将多个目标文件和所需的库文件链接在一起,生成最终的可执行文件的过程。
符号解析:将各个目标文件中的符号引用与其定义进行关联,解决符号引用问题。
重定位:将目标文件中的地址引用转换为实际的内存地址。
合并代码和数据:将各个目标文件中的代码和数据合并到一起,生成最终的可执行文件。
链接过程中还会处理程序的静态库和动态库依赖关系,确保程序在运行时所需的库文件已经被正确地加载。
总结
通过预处理、编译、汇编和链接这四个阶段,源代码最终被转换为可执行文件,并在计算机上运行。在实际的软件开发中,这些阶段通常由编译器和链接器自动完成。了解文件编译的过程有助于程序员更好地理解程序的底层机制和运行原理,从而更好地优化代码和提高编译效率。
23、虚拟内存
虚拟内存是计算机系统内存管理的一种技术,它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上这部分内存是被分隔成多个物理内存碎片的,有时部分数据还会暂时存储在外部磁盘存储器上,在需要时进行数据交换。以下是对虚拟内存的详细解释:
一、定义与概念
虚拟内存是操作系统用来扩展可用内存容量的一种技术。它通过将部分数据暂时存储在硬盘上,使得超出物理内存限制的数据也能被有效使用。每个程序都使用虚拟地址空间来访问内存,而非直接访问物理内存。虚拟内存管理器负责将虚拟地址转换为物理地址。
二、工作原理
虚拟内存的核心在于分页(Paging)技术。操作系统将内存划分为固定大小的块,称为页面(Page),每一页通常为4KB。在实际运行时,操作系统会将所需的页面从虚拟内存调入物理内存。当某个页面不再需要时,它会被换出(Swapped Out)到虚拟内存。以下是虚拟内存工作原理的详细步骤:
地址映射:每个进程都有自己的页面表,用于记录虚拟地址和物理地址的映射关系。当进程访问内存时,CPU会使用页面表来转换虚拟地址为物理地址。
页面调度:当程序需要访问一个页面时,虚拟内存管理器会检查该页面是否已经在物理内存中。如果已经在物理内存中,则直接访问;如果不在,则发生页面错误(Page Fault)。
页面错误:当进程访问的页面不在物理内存中时,会发生页面错误。操作系统会暂停进程,将所需的页面从虚拟内存调入物理内存(通常是从硬盘上的页面文件中读取),然后恢复进程的执行。
页面置换:当物理内存已满时,操作系统会选择不常用的页面进行置换,将其换出到虚拟内存(即硬盘上的页面文件中),从而腾出空间给新的页面。页面置换算法有多种,如最近最少使用(LRU)等。
三、优点与缺点
优点
扩展内存空间:虚拟内存使得计算机能够运行比物理内存更大的应用程序,有效扩展了系统的内存容量。
提高多任务处理能力:通过虚拟内存,操作系统可以在有限的物理内存上运行多个程序,提高了系统的多任务处理能力。
提高内存利用率:虚拟内存允许程序使用比物理内存更多的内存,而不会因此而崩溃。同时,通过页面置换算法,可以更有效地利用物理内存空间。
简化内存管理:虚拟内存为程序员提供了一个更大的、连续的地址空间,简化了内存管理的工作。
缺点
占用一定的物理硬盘空间:虚拟内存需要在硬盘上存储页面文件,因此会占用一定的物理硬盘空间。
加大了对硬盘的读写:由于页面置换和页面调度等操作,虚拟内存会加大对硬盘的读写频率,可能会影响硬盘的寿命和性能。
设置不当会影响整机稳定性与速度:如果虚拟内存设置不当(如页面文件大小设置不合理、虚拟内存与系统设在同一分区内等),可能会影响整机的稳定性和速度。
四、应用场景
大型数据处理:在处理大数据集或运行大型应用程序时,虚拟内存可以提供更大的内存空间,避免内存不足的问题。
多任务操作:虚拟内存允许同时运行多个应用程序,提高了多任务处理能力,适用于需要同时运行多个程序的场景。
内存保护:通过虚拟内存提供的内存保护机制,可以防止不同进程之间的内存互相干扰,从而提高系统稳定性和安全性。
五、配置与优化
根据内存大小和电脑用途设定:虚拟内存的设定主要根据电脑的内存大小和用途来设定。一般来说,可以让操作系统自动分配管理虚拟内存,它能根据实际内存的使用情况动态调整虚拟内存的大小。
避免与系统设在同一分区内:为了避免系统在此分区内进行频繁的读写操作而影响系统速度,最好将虚拟内存设置在其它分区中磁盘剩余空间较大而又不常用的盘中(如D、E盘)。
合理配置虚拟内存大小:一般默认的虚拟内存大小是取一个范围值,但最好给它一个固定值以减少磁盘碎片的产生(但需要注意的是固态硬盘不会产生磁盘碎片)。具体数值可以根据物理内存大小来定。
选择速度较快的硬盘或SSD:为了提高虚拟内存的性能,可以选择速度较快的硬盘或固态硬盘(SSD)来存储页面文件。
综上所述,虚拟内存是计算机系统内存管理的一种重要技术,它通过分页技术和页面置换算法实现了内存扩展和多任务处理等功能。虽然虚拟内存有一定的缺点和限制,但通过合理的配置和优化,可以充分发挥其优势并提高系统的性能和稳定性。
24、浏览器输入网址后执行的过程?
当在浏览器中输入网址并按下回车键时,以下是大致的执行过程:
DNS解析:浏览器首先将输入的网址发送给DNS服务器,以获取该网址对应的IP地址。DNS服务器会查询其数据库,在找到匹配的域名时返回对应的IP地址给浏览器。
TCP连接建立:浏览器使用获取到的IP地址与服务器建立TCP连接。这涉及到使用TCP三次握手的过程,确保客户端与服务器之间的可靠连接。
发起HTTP请求:一旦建立了TCP连接,浏览器会发送HTTP请求到服务器。请求包含请求方法(例如GET、POST)、请求的URL、HTTP版本以及其他可能的请求头信息,如用户代理、Cookie等。
服务器处理请求:服务器收到HTTP请求后,会根据请求的URL和其他请求信息来处理请求。服务器可能会读取请求中的参数,查询数据库,执行相应的逻辑处理,并生成HTTP响应。
HTTP响应:服务器生成完整的HTTP响应后,将其返回给浏览器。响应包括一个状态码表示请求的结果(例如200表示成功,404表示资源未找到等),响应的内容,以及其他响应头信息,如Content-Type、Content-Length等。
接收和解析响应:浏览器接收到服务器的HTTP响应后,开始解析响应。它会检查状态码,根据响应头中的Content-Type确定响应内容的类型,并将响应的内容保存下来。
渲染页面:如果响应的内容是HTML页面,浏览器会开始解析HTML,并构建DOM树。然后,将CSS文件加载和解析为样式规则,并将其应用于DOM树,生成渲染树。最后,浏览器使用渲染树将页面内容绘制到用户的屏幕上。
关闭TCP连接:一旦页面完全加载并渲染完成,浏览器会关闭与服务器之间的TCP连接。但是,如果页面中存在其他的资源(如图片、脚本、样式表等),浏览器可能会继续发送HTTP请求获取这些资源。