1、C/C++内存模型
1. 栈:栈⽤于存储函数的局部变量、函数参数和函数调⽤信息的区域。函数的调⽤和返回通过栈来管理。向下增长。2. 堆:堆⽤于存储动态分配的内存的区域,由程序员⼿动分配和释放。使⽤ new 和 delete 或 malloc 和 free 来进⾏堆内存的分配和释放。向上增长。3. 全局/静态区: 全局区存储全局变量和静态变量。⽣命周期是整个程序运⾏期间。在程序启动时分配,程序结束时释放。4. 常量区: 常量区也被称为只读区。存储常量数据,如字符串常量。5. 代码区:存储程序的代码。
2、编译的四个过程
1、预处理:引入头文件,宏替换,删除注释…生成以.i为结尾的预编译文件
2、编译:检查语义语法规错误,无错误则生成以.s为结尾的汇编文件
3、汇编:将汇编代码解释为二进制的cpu指令,生成.o为结尾的可重定向目标文件
4、链接: 将多个目标文件及所需要的库连接成最终的可执行目标文件
什么是extern"C"?
可以在 C++中使用C 的已编译好的函数模块,这时候就需要用到 extern”C” 。也就是 extern“C” 都是在c++ 文件里添加的。extern 在链接阶段起作用(四大阶段:预处理 -- 编译 -- 汇编 -- 链接)。
3、c++11的新特性
找出了比较简单的几条:
- 1、用auto关键字来支持自动类型推导
- 2、引入了范围for循环,提供了一种简洁而直观的方式来遍历容器、数组、字符串和其他可迭代对象。比如for(char c : str){..}。
- 3、引入了新的智能指针类(独占式、共享式、弱引用),用于更安全和方便地管理动态分配的资源,避免内存泄漏和悬空指针等问题。
- 4、统一初始化:直接初始化、拷贝初始化、列表初始化
- 5、nullptr是c++11用来表示空指针新引入的常量值,在c++中如果表示空指针语义时建议使用nullptr而不要使用NULL,因为NULL本质上是个int型的0,其实不是个指针
- 6、lambda匿名函数
- 7、右值引用
- 8、原子操作
4、malloc/free和new/delete的区别
- malloc和free是函数(C/C++),new和delete是操作符(C++)。
new
和delete
在 C++ 中提供了更高级、更类型安全的内存管理功能。- 申请空间:使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
- 类型强转与否:malloc的返回值为void*, 在使用时必须强转;而new后跟的是空间的类型,就不需要强转。说明:new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
- 申请空间失败时:malloc返回的是NULL;而new会抛异常。
- 申请自定义类型对象时:malloc/free只会开辟空间,不会调用构造函数与析构函数,而new会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。
在1G内存的计算机中能否malloc(1.2G)?为什么?
是有可能申请 1.2G 的内存的。应用程序通过 malloc 函数可以向程序的虚拟空间申请一块虚拟地址空间,与物理内存没有直接关系,得到的是在虚拟地址空间中的地址,之后程序运行所提供的物理内存是由操作系统完成的。
什么是虚拟内存?
虚拟内存是一种计算机系统内存管理技术,它为应用程序提供了一个连续可用的内存空间,但实际上,这部分内存可能分布在物理内存的不同碎片中,甚至部分暂时存储在外部磁盘存储器上。
5、什么是内存泄漏,怎么避免?
内存泄漏:程序未能释放掉不再使用的内存
- 养成良好的编码规范,申请的内存空间记着匹配的去释放内存
- 使用内存泄漏工具检测(在linux环境下可以使用内存泄漏检测工具Valgrind,我们写代码是可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致。)
- 采用RAII思想或者智能指针来管理资源
6、野指针是什么?悬空指针是什么?
- 野指针:野指针指访问一个已删除或访问受限的内存区域的指针,野指针不能判断是否为NULL来避免。指针没有初始化,释放后没有置空,越界。(指向内存被释放的内存或者没有访问权限的内存的指针。)
- 悬空指针:一个指针的指向对象已被删除,那么就成了悬空指针
“野指针”的成因主要有3种
①、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
char *p = NULL; char *str = new char(100);
②、指针p被free或者delete之后,没有置为NULL;
③、指针操作超越了变量的作用范围。
如何避免野指针
(一)指针初始化
①将指针初始化为NULL。
char * p = NULL;
②用malloc分配内存
char * p = (char * )malloc(sizeof(char));
③用已有合法的可访问的内存地址对指针初始化
char num[ 30] = {0};
char *p = num;
(二)指针用完后释放内存,将指针赋NULL。
delete(p);
p = NULL;
7、传参:值传递、引用传递、指针传递
值传递:
1、当函数不需要修改原始值,只是使用该值时,值传递也是一个不错的选择:函数内部对参数的修改不会影响到原始值,确保了数据的安全性。
2、对于简单的数据类型,如整数、字符等,值传递是最合适的选择:
3、需要额外的内存来存储参数的副本,对于大型对象的传递,值传递会导致性能下降,可能会导致内存消耗过大。
引用传递:
- 当函数需要修改原始值时,引用传递是最常用的方式。
- 对于大型对象的传递,引用传递可以提高性能
- 无法传递空值,需要保证引用的有效性
- 不需要手动管理内存
指针传递:
1、当函数需要修改原始值时,指针传递是一个不错的选择:指针传递是通过将参数的内存地址传递给函数来实现的。函数可以通过指针来直接访问和修改原始值。
2、对于需要传递大型对象的情况,指针传递可以提高性能。
3、指针可能为空或者指向无效的内存地址,需要进行有效性检查。
4、需要手动管理内存
指针参数传递本质上是值传递,它所传递的是一个地址值。值传递过程中,被调用函数的形式参数被作为被调用函数的局部变量处理,会在栈中开辟内存空间以存放有主调函数传递进来的实参值,从而形成了实参的一个副本。值传递的特点是:被调函数对形式参数的任何操作都是作为局部变量进行的,不会影响主调函数的实参变量的值(形参指针变了,实参指针不会变)。
引用参数传递过程中,被调函数的形式参数作为局部变量在栈中开辟了内存空间,但这时存放的是由主调函数放进的实参变量的地址。被调用函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数的实参变量。如果想通过指针参数传递来改变主调函数中的相关变量,那就得使用指向指针的指针或指针引用。
指针和引用的区别?
指针:变量,独立,可变,可空,替身,无类型检查;
引用:别名,依赖,不变,非空,本体,有类型检查;
在C++中,指针和引用是两种用于间接访问内存地址的机制,但它们之间存在一些重要的区别。
定义与初始化:指针:指针是一个变量,其值为另一个变量的地址。指针在使用前必须被初始化,否则它可能指向一个随机的内存地址,这可能导致未定义的行为。引用:引用是已存在变量的别名。引用在定义时必须被初始化,且一旦初始化后,就不能再指向其他变量。
可空性:指针:指针可以为空(nullptr),表示它不指向任何对象。引用:引用在定义时必须指向一个有效的对象,不能为空。
可重赋值性:指针:指针的值(即它所指向的地址)可以在任何时候被改变,使其指向另一个对象。引用:引用的值(即它所引用的对象)在初始化后不能被改变。一旦一个引用被初始化为引用一个对象,它就不能再引用另一个对象。
内存占用:指针:指针本身是一个变量,会占用一定的内存空间(通常与机器的字大小相同)。引用:引用不占用额外的内存空间,它只是一个别名,与所引用的对象共享同一块内存。
运算:指针:可以对指针进行各种运算,如加法、减法、比较等,以改变它所指向的地址。引用:引用不支持这样的运算,它只是一个对象的别名,可以像操作对象本身一样操作引用。
用途:指针:常用于动态内存分配、函数参数传递(尤其是当需要修改原始数据时)、数据结构(如链表、树等)的实现等。引用:常用于函数参数传递(当不需要修改原始数据,但希望避免数据拷贝时)、类成员变量的别名等。
8、static的用法
1 )用 static 修饰局部变量:使其变为静态存储方式 (静态数据区 ),那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中。2 )用 static 修饰全局变量:使其只在本文件内部有效,而其他文件不可连接或引用该变量。3 )用static修饰函数:对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的(这一点在大工程中很重要很重要,避免很多麻烦,很常见)。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。
9、const的用法——只读!
const 主要用来修饰变量、函数形参和类成员函数:1 )用 const修饰常量:定义时就初始化,以后不能更改,只读。2 )用 const 修饰形参: func(const int a){}; 该形参在函数里不能改变,只读3 )用 const 修饰类成员函数:该函数对成员变量只能进行只读操作,就是 const类成员函数是不能修改成员变量的数值的。被 const 修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
const和define的区别
- 用#define max 100 ; 定义的常量是没有类型的(不进行类型安全检查,可能会产生意想不到的错误), 所给出的是一个立即数,编译器只是把所定义的常量值与所定义的常量的名字联系起来,define所定义 的宏变量在预处理阶段的时候进行替换,在程序中使用到该常量的地方都要进行拷贝替换。
- 用const int max = 255 ; 定义的常量有类型(编译时会进行类型检查)名字,存放在内存的静态区域 中,在编译时确定其值。在程序运行过程中const变量只有一个拷贝,而#define所定义的宏变量却有多 个拷贝,所以宏定义在程序运行过程中所消耗的内存要比const变量的大得多
10、volatile的用法
与const对立,一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量在内存中的值,而不是使用保存在寄存器里的备份(虽然读写寄存器比读写内存快)。
- 提醒编译器不要对该变量相关的代码进行优化,避免出现意外的负面作用;
- 对类似的表达式不进行编译层面的指令重排。编译指令重排也是一种编译器优化手段,这条严格来说也是第一条的变种。
以下几种情况都会用到 volatile :1、并行设备的硬件寄存器(如:状态寄存器)2、一个中断服务子程序中会访问到的非自动变量3、多线程应用中被几个任务共享的变量
多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程可见。
读取变量在内存里的值,而不要读它的备份。
11、逻辑运算符
if (A && B) 如果 A 为 false ,整个表达式就为 false,不再计算 B 的值了。
if (A & B) 如果 A 为 false ,整个表达式就为 false,但还要计算 B 的值。
if (A && B++) 如果A 为 false,&&不会再计算后面的值
if (A & B++) 如果A 为 false,&则会计算后面的值
||也具有“短路效果”
12、sizeof、strlen
sizeof(变量),是一个操作符,返回一个对象或者类型所占的内存字节数,而不是元素个数!因为还和元素的类型有关。如果对象是指针类型,指针是占4个字节的空间(对32位机)。
strlen(str),是一个函数,返回字符串的长度。直到'\0'为止了,计数结果不包括\0。
13、sizeof(struct)和sizeof(union)内存对齐
内存对齐作用:
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存, 处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
- 对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员其当前偏移量必须是当前成员类 型的整数倍
- 整个结构体占用内 存大小是结构体内最大数据成员的最小整数倍
- 如程序中有#pragma pack(n)预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再 考虑当前类型以及最大结构体内类型
联合体union的大小
- 找到占用字节最多的成员;
- union的字节数必须是占用字节最多的成员的字节的倍数,而且需要能够容纳其他的成员
13、inline内联函数
14、友元函数
友元函数是指某些虽然不是类成员却能够访问类的所有成员的函数。类授予它的友元特别的访问权。
在 C++ 中,友元函数是指在类的声明中使用 `friend` 关键字声明的非成员函数,友元函数可以访问该类的私有成员。它们不是类的成员函数,但具有访问类的私有成员的特权。
- 直接访问:友元函数可以访问类的私有成员和保护成员,这使得在某些情况下,当其他类的成员函数需要直接访问这些成员时,可以通过声明友元函数来实现,而无需通过类的公有接口进行间接访问。
- 灵活性:通过友元函数,可以灵活地实现一些特殊操作,这些操作可能不适合作为类的成员函数,但又需要访问类的私有成员。
- 减少封装开销:在某些性能敏感的场景中,通过友元函数直接访问类的私有成员,可以避免通过公有接口进行间接访问所带来的封装开销,从而提高程序的执行效率。
更常见的做法是在内部声明,然后在外部定义;友元函数的声明(无论是内部还是外部)都使用friend
关键字。表示该函数是类的友元,能够访问类的私有(private)和保护(protected)成员。然而,友元函数的定义(即其实现)通常放在类的外部,就像普通的非成员函数一样。
15、指针
16、面对对象
封装是指将数据(属性)和行为(方法)捆绑在一起,形成一个对象,并通过公共接口来访问这个对象。封装的目的是保护对象的内部状态,防止外部直接访问和修改对象的数据,确保数据的完整性和程序的安全性。
具体掌握内容:1、即隐藏对象的内部实现细节,只暴露必要的接口供外部使用。2、public、private(默认)、protected;3、熟悉封装在面向对象编程中的实践,如使用getter和setter方法来访问和修改私有属性。-------被protected修饰的类、方法、变量或者接口只能被相同包或其子类中的类或对象所访问。
继承是允许新的类(子类)继承一个现有类(父类)的属性和方法。继承促进了代码的复用和模块化,通过继承一个基类,子类可以继承基类的所有特性,同时还可以添加新的特性或者覆盖基类中的方法。
具体掌握内容:掌握继承的语法和规则;了解继承的层次结构和类之间的关系,包括单继承和多继承;熟悉方法重写的概念,即子类可以覆盖父类中的方法以提供特定的实现。
多态是指同一个行为具有多个不同表现形式的特性。在面向对象编程中,多态通常通过继承和接口来实现,允许不同类的对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用不同的处理方式。
具体掌握内容:理解多态的基本概念和作用,即提高代码的灵活性和可扩展性。掌握多态的两种类型:编译时多态(静态多态,通过方法重载实现)和运行时多态(动态多态,通过继承和接口实现)。熟悉多态在面向对象编程中的实践,如使用父类引用指向子类对象,并通过该引用调用子类重写的方法。
public、 protected、private三者在继承情况下的访问权限
public
- 当一个成员(无论是数据成员还是成员函数)被声明为
public
时,它可以在任何地方被访问:在类的内部、从类的派生类以及通过类的对象。 - 在继承中,如果基类中的成员是
public
的,那么在派生类中,这个成员也是public
的。
protected
protected
成员可以在类的内部和从类的派生类中被访问,但不能通过类的对象来访问。- 在继承中,如果基类中的成员是
protected
的,那么在派生类中,这个成员也是protected
的。这意味着派生类的成员函数和友元可以访问它,但派生类的对象或派生类之外的代码不能访问它。
private
private
成员只能在类的内部被访问,无论是从派生类还是通过类的对象都不能访问。- 在继承中,如果基类中的成员是
private
的,那么在派生类中,这个成员是不可见的。这意味着派生类不能访问这个成员,无论它是通过继承获得的,还是通过其他方式。
继承中的访问权限遵循以下规则:
- 公有继承(public inheritance):基类的
public
和protected
成员在派生类中保持其原有的访问权限。基类的private
成员在派生类中是不可见的。 - 保护继承(protected inheritance):基类的
public
和protected
成员在派生类中变为protected
。基类的private
成员在派生类中是不可见的。 - 私有继承(private inheritance):基类的所有成员(
public
、protected
和private
)在派生类中都变为private
。这意味着即使基类的成员在基类中是public
或protected
的,派生类也不能直接访问它们。
原子的操作
是C11新特性。提供了一组函数和类型,用于支持多线程并发访问共享变量时的原子性操作。原子操作是在不被中断的情况下执行的操作,保证了对共享变量的操作不会被其他线程的操作所干扰。
原子操作指的是由多步操作组成的一个操作,在执行过程中具有不可分割性。即该操作要么完全执行,要么完全不执行,不存在只执行部分步骤的情况。原子操作在并发编程中非常重要,用于保证数据的一致性和完整性。在多核系统中,由于多个CPU核可能同时访问同一内存区域,因此需要使用特定的机制(如内存屏障)来实现原子操作,以避免数据竞争和不一致性。
原子类型:atomic_bool, atomic_char, atomic_int, atomic_long, atomic_llong, atomic_short, atomic_float, atomic_double, atomic_ptr 等,这些类型用于创建对应类型的原子变量
单继承、多重继承、虚继承
- 单继承:一个子类只有一个直接父类时,称这个继承关系为单继承。在单继承中,子类会继承父类的所有属性和方法(除非被重写或隐藏)。
- 多重继承:一个子类有两个或两个以上直接父类时,称这个继承关系为多继承。多继承允许子类同时继承多个父类的属性和方法,但也可能导致数据冗余和二义性问题。
- 虚继承:虚继承是为了解决多重继承中的二义性问题而引入的一种机制。在虚继承中,子类只继承父类的一个共享实例,而不是每个父类都创建一个独立的实例。这样可以避免数据冗余和冲突,但也可能增加系统的复杂性和开销。
左值、右值
- 左值通常指的是具有持久状态的对象,可以取地址、有名字,并且可以出现在赋值操作符的左侧。左值引用就是给左值取别名,它允许我们通过一个引用来操作原始的左值对象,但并不会创建对象的副本。左值引用在C++中非常常见,用于函数参数传递、返回值以及成员变量等场景。
- 右值引用(Rvalue Reference)是C++11中新增的一种引用类型,用于对右值(Rvalue)进行引用。右值通常指的是临时对象、字面量值或者即将被销毁的对象,它们不能取地址,且没有名字,通常出现在赋值操作符的右侧。右值引用为了移动语义和完美转发。
左值可以取地址,右值不能被取地址
动态链接库
动态链接库(Dynamic Link Library,简称DLL)是微软公司在Windows操作系统中实现共享函数库概念的一种实作方式。动态链接库允许程序在运行时动态地加载和链接所需的代码和数据,而不是在编译时静态地链接到可执行文件中。这样可以节省磁盘空间和内存资源,并且可以实现代码的模块化和重用。动态链接库广泛应用于Windows系统中的各种应用程序和组件中。
十大排序
快速排序、归并排序、堆排序、 冒泡排序、插入排序、选择排序、希尔排序、桶排序、基数排序
十大排序中考察最多的就是冒泡排序、快速排序、归并排序、堆排序以及可能会出现的桶排序和基数排 序了,其余排序出现的概率稍小一点
分类:
- 非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因 此称为非线性时间比较类排序。
- 线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界, 以线性时间运行,因此称为线性时间非比较类排序
稳定性:
- 稳定:如果 a 原本在 b 前面,而 a = b,排序之后 a 仍然在 b 的前面;
- 不稳定:如果 a 原本在 b 的前面,而 a = b,排序之后 a 可能会出现在 b 的后面;
排序方式:
- 内排序:所有排序操作都在内存中完成,占用常数内存,不占用额外内存。
- 外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行,占用额 外内存。
复杂度:
- 时间复杂度: 一个算法执行所耗费的时间。
- 空间复杂度: 运行完一个程序所需内存的大小。
1. 快速排序(Quick Sort)
- 时间复杂度:平均情况:O(n log n) 最坏情况:O(n^2)(当数组已经有序或完全逆序时) 最好情况:O(n log n)(理论上,但实现上很难达到)
- 空间复杂度:O(log n)(递归栈空间,平均情况),最坏情况下为O(n)(当递归分治极不平衡时)
- 稳定性:不稳定
2. 归并排序(Merge Sort)
- 时间复杂度:O(n log n)
- 空间复杂度:O(n)(合并过程中需要额外的数组空间)
- 稳定性:稳定
3. 堆排序(Heap Sort)
- 时间复杂度:O(n log n)
- 空间复杂度:O(1)(除了几个变量外,不需要额外的存储空间)
- 稳定性:不稳定
4. 冒泡排序(Bubble Sort)
- 时间复杂度:平均情况与最坏情况:O(n^2) 最好情况:O(n)(当数组已经有序时)
- 空间复杂度:O(1)(原地排序)
- 稳定性:稳定
5. 插入排序(Insertion Sort)
- 时间复杂度:平均情况与最坏情况:O(n^2) 最好情况:O(n)(当数组已经有序时)
- 空间复杂度:O(1)(原地排序)
- 稳定性:稳定
6. 选择排序(Selection Sort)
- 时间复杂度:O(n^2)
- 空间复杂度:O(1)(除了几个变量外,不需要额外的存储空间)
- 稳定性:不稳定
7. 希尔排序(Shell Sort)
- 时间复杂度:取决于增量序列的选择,最好的增量序列时间复杂度可以达到O(n log^2 n) 平均情况与最坏情况:依赖于增量序列,但通常认为是O(n1.5)至O(n2)
- 空间复杂度:O(1)(原地排序)
- 稳定性:不稳定
8. 桶排序(Bucket Sort)
- 时间复杂度:平均情况:O(n+k)(k是桶的数量) 最坏情况:O(n^2)(当所有元素分配到同一个桶中时)
- 空间复杂度:O(n+k)(需要额外的桶存储空间)
- 稳定性:稳定(如果使用稳定的排序算法对每个桶进行排序)
9. 基数排序(Radix Sort)
- 时间复杂度:O(nk)(k是数字的位数,n是数组长度)
- 空间复杂度:O(n+k)(需要额外的空间来存储基数排序的桶)
- 稳定性:稳定(如果采用稳定的计数排序或桶排序作为子过程)
什么是堆,栈,内存泄漏和内存溢出?
栈由系统操作,程序员不可以操作。 所以内存泄漏是指堆内存的泄漏。
堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程 序运行期决定),使用完后必须显式释放的内存。应用程序一般使用malloc,new等函数从堆中分配到 一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被 再次使用。
内存溢出:你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
内存越界:向系统申请了一块内存,而在使用内存时,超出了申请的范围(常见的有使用特定大小数组 时发生内存越界) 内存溢出问题是 C 语言或者 C++ 语言所固有的缺陷,它们既不检查数组边界,又不检查类型可靠性 (type-safety)。
众所周知,用 C/C++ 语言开发的程序由于目标代码非常接近机器内核,因而能够直接访问内存和寄存器,这种特性大大提升了 C/C++ 语言代码的性能。只要合理编码,C/C++ 应用程序在执行 效率上必然优于其它高级语言。然而,C/C++ 语言导致内存溢出问题的可能性也要大许多。
死锁的原因、条件
产生死锁的原因主要是: (1) 因为系统资源不足。 (2) 进程运行推进的顺序不合适。 (3) 资源分配不当等。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限 的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁 这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足, 就不会发生死锁。 (1) 互斥条件:一个资源每次只能被一个进程使用。 (2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 (3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。 (4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系