1. 进程和线程的基本概念是什么?
- 进程:进程是操作系统分配资源的基本单位,是一个正在运行的程序实例。每个进程都有自己的内存空间、文件句柄和其他资源。
- 线程:线程是进程的一个执行单元,是CPU调度和分派的基本单位。一个进程可以包含多个线程,这些线程共享进程的资源,如内存空间。
2. 进程和线程之间有什么主要区别?
- 资源:进程有独立的内存空间和系统资源,而线程共享进程的内存和资源。
- 通信:进程之间的通信相对复杂(如使用IPC),而线程之间的通信比较简单(共享内存即可)。
- 创建和销毁:创建和销毁进程的开销较大,线程的创建和销毁相对轻量级。
- 调度:进程的调度开销较大,因为进程切换涉及内存页的切换;线程切换的开销较小。
3. 进程和线程的优缺点分别是什么?
- 进程的优点:
- 独立的内存空间,进程之间相互隔离,安全性好。
- 稳定性高,一个进程崩溃不会影响其他进程。
- 进程的缺点:
- 创建和切换开销大。
- 进程间通信复杂。
- 线程的优点:
- 创建和切换开销小。
- 线程之间通信方便。
- 线程的缺点:
- 共享内存空间可能引发数据竞争和安全问题。
- 一个线程崩溃可能导致整个进程崩溃。
4. 什么时候选择使用进程,什么时候选择使用线程?
- 使用进程:
- 当需要高度隔离的任务时(例如安全要求高的任务)。
- 需要利用多核CPU进行并行处理时。
- 任务彼此独立、错误不会相互影响时。
- 使用线程:
- 当任务需要共享大量数据时(如共享内存)。
- 需要并行执行且相互之间通信频繁时。
- 希望降低上下文切换开销时。
5. 多进程和多线程的同步与通信方法有哪些?
- 多进程同步和通信:
- 同步:信号量(semaphore)、文件锁(file lock)、共享内存(shared memory)等。
- 通信:管道(pipe)、消息队列(message queue)、共享内存、套接字(socket)、信号(signal)。
- 多线程同步和通信:
- 同步:互斥锁(mutex)、读写锁(read-write lock)、条件变量(condition variable)、自旋锁(spinlock)。
- 通信:线程之间主要通过共享内存和同步原语进行通信。
6. 进程的地址空间模型有哪些?
- 单一地址空间:进程的所有代码、数据和堆栈在同一个地址空间内,适用于简单的嵌入式系统。
- 分段地址空间:内存分为多个段(如代码段、数据段、堆栈段),每个段有自己的地址空间。
- 分页地址空间:内存按固定大小的页划分,进程地址空间是虚拟的,通过页表映射到物理内存。
7. 进程和线程的状态转换图是什么样的?什么情况下会阻塞和就绪?
- 进程/线程状态:
- 就绪(Ready):等待被CPU调度。
- 运行(Running):正在CPU上执行。
- 阻塞(Blocked):等待某个事件完成(如I/O操作)。
- 结束(Terminated):进程/线程执行完毕或被强制结束。
- 状态转换:
- 就绪 -> 运行:被调度器选中,获得CPU时间。
- 运行 -> 阻塞:等待I/O或其他事件。
- 阻塞 -> 就绪:等待的事件完成。
- 运行 -> 结束:正常结束或出现异常。
8. 父进程和子进程之间的关系和区别是什么?
- 关系:子进程是通过父进程调用
fork()
或其他进程创建函数创建的,子进程继承父进程的大部分资源(如文件描述符、环境变量等)。 - 区别:
- 子进程有独立的地址空间和进程ID。
- 子进程可以与父进程并行执行,或者等待父进程的指令。
9. 什么是进程上下文和中断上下文?
- 进程上下文:进程正在运行时的状态,包括CPU寄存器、程序计数器、内存映射等。操作系统通过保存和恢复这些上下文来实现进程的切换。
- 中断上下文:当硬件或软件中断发生时,CPU保存当前的执行状态,转而执行中断处理程序。中断上下文通常没有用户空间的地址映射,只能访问内核空间。
10. 一个进程可以创建多少线程?这个数量与什么有关?
- 理论上,一个进程可以创建非常多的线程,实际数量受限于以下因素:
- 系统资源限制(如内存、文件描述符数量等)。
- 操作系统限制(如Linux中的
ulimit
)。 - 每个线程的栈空间大小,越大的栈空间会限制能创建的线程数量。
11. 并发、同步、异步、互斥、阻塞和非阻塞的定义是什么?
- 并发:多个任务在同一时间段内交替执行,任务之间可能重叠。
- 同步:任务按顺序执行,一个任务必须等待另一个任务完成。
- 异步:任务可以独立执行,不必等待其他任务完成。
- 互斥:多个任务在同一时间只能有一个任务访问共享资源,防止资源冲突。
- 阻塞:任务在某种条件未满足时会等待,直到条件满足后继续执行。
- 非阻塞:任务在等待某种条件时不会阻塞,而是继续执行其他操作。
12. 线程同步和互斥的具体实现方法有哪些?
- 线程同步:
- 条件变量(Condition Variable)
- 信号量(Semaphore)
- 事件(Event)
- 线程互斥:
- 互斥锁(Mutex)
- 读写锁(Read-Write Lock)
- 自旋锁(Spinlock)
13. 线程同步与阻塞之间的关系是什么?同步一定阻塞吗?阻塞一定同步吗?
- 关系:线程同步往往需要阻塞线程,直到满足某种条件后再继续执行。
- 同步一定阻塞吗?:同步不一定阻塞,例如使用自旋锁可以实现非阻塞同步。
- 阻塞一定同步吗?:阻塞不一定是为了同步,可能是因为等待I/O或资源可用。
14. 孤儿进程、僵尸进程和守护进程的概念是什么?
- 孤儿进程:父进程退出但子进程仍然在运行,此时子进程成为孤儿进程,会被
init
进程接管。 - 僵尸进程:子进程退出后,父进程未调用
wait()
获取子进程的终止状态,子进程的条目仍保留在进程表中。 - 守护进程:一种在后台运行的长期服务进程,通常在系统启动时启动,独立于终端。
15. 如何创建守护进程?
- 步骤:
- 调用
fork()
创建子进程,父进程退出。 - 在子进程中调用
setsid()
创建新的会话,并成为会话领导进程。 - 修改工作目录、文件权限掩码等,关闭不需要的文件描述符。
- 子进程进入后台运行,通常通过循环执行。
- 调用
16. 如何正确处理僵尸进程?
- 方法:
- 父进程使用
wait()
或waitpid()
来获取子进程的终止状态,释放其占用的资源。 - 如果父进程不关心子进程的退出状态,可以使用
signal(SIGCHLD, SIG_IGN)
忽略SIGCHLD
信号。
- 父进程使用
17. C和C++之间的主要区别是什么?
- C:
- 过程式编程语言,强调函数和
过程调用。
- 不支持类和对象的概念。
- C++:
- 支持面向对象编程,引入了类、继承、多态等概念。
- 支持函数重载、运算符重载等特性。
- 提供了标准模板库(STL)。
18. new和malloc的区别是什么?
- new:
- 是C++中的运算符,分配内存并调用构造函数。
- 返回对象的指针,可以自动调用相应的构造函数。
- malloc:
- 是C语言的函数,分配原始的内存块,不调用构造函数。
- 返回
void*
指针,需要手动类型转换。
19. malloc的底层实现是怎样的?
- malloc:
- 底层通过系统调用
brk()
或mmap()
向操作系统请求内存。 - 内部维护一个空闲链表来管理已分配和未分配的内存块。
- 分配内存时,从空闲链表中找到合适的块,并进行分割或合并。
- 底层通过系统调用
20. 在1G内存的计算机中能否malloc(1.2G)?为什么?
- 不可以:因为
malloc(1.2G)
需要的内存超过了物理内存大小,操作系统无法分配这么大的连续内存块,除非有足够的交换空间并且操作系统支持大内存映射。
21. 指针与引用的相同和区别是什么?如何相互转换?
- 相同:
- 都是指向某一内存地址,可以通过它们访问对象。
- 区别:
- 指针是独立的变量,可以为空,可以重新指向其他对象。
- 引用是对象的别名,初始化后不可更改,不能为空。
- 转换:指针可以通过解引用
*
转换为引用,引用可以通过取地址符号&
转换为指针。
22. C语言检索内存情况的方式有哪些?内存分配的方式是什么?
- 检索内存:
sizeof()
:返回数据类型或变量的大小。malloc_stats()
:输出当前内存分配的统计信息。
- 内存分配:
- 静态分配:在编译时分配,如全局变量、静态变量。
- 栈内存分配:在函数调用时分配,函数退出时自动释放。
- 堆内存分配:动态分配,通过
malloc()
、calloc()
、realloc()
分配,由程序员手动释放。
23. extern "C"的作用是什么?
- 作用:在C++中,
extern "C"
用于告诉编译器按照C语言的方式编译和连接代码,以便C++代码可以与C代码链接或调用C语言编写的库。
24. 头文件声明时加extern,而在定义时不要加的原因是什么?
- 原因:在头文件中使用
extern
声明变量或函数,是为了告诉编译器这些变量或函数在其他地方定义。在定义时不需要extern
,直接给出变量或函数的内存空间分配。
25. 函数参数压栈顺序是什么?关于__stdcall和__cdecl调用方式的理解是什么?
- 压栈顺序:通常是从右到左压栈,最后压栈的参数是函数的第一个参数。
- __stdcall:调用者清理栈,参数从右到左压栈,多用于Windows API函数。
- __cdecl:被调用者清理栈,参数从右到左压栈,是C语言默认的调用约定。
26. 重写memcpy()函数需要注意哪些问题?
- 问题:
- 源和目标内存区域是否重叠。
memcpy
假定区域不重叠,如果重叠需使用memmove
。 - 内存对齐问题,确保访问内存时不会发生未对齐访问。
- 边界检查,确保不超出目标缓冲区的大小。
- 源和目标内存区域是否重叠。
27. 数组到底存放在哪里?
- 存放位置:
- 全局或静态数组存放在数据段(BSS或已初始化数据段)。
- 局部数组存放在栈中。
- 动态分配的数组(如通过
malloc
或new
分配)存放在堆中。
28. struct和class的区别是什么?
- 区别:
- 默认访问权限:
struct
的成员默认是public
,class
的成员默认是private
。 - 继承默认访问权限:
struct
默认是public
继承,class
默认是private
继承。 - 语法层面没有其他区别,两者在C++中功能相同。
- 默认访问权限:
29. char和int之间的转换是怎样的?
- 转换:
char
通常是一个8位的整数类型,范围为-128
到127
(有符号)或0
到255
(无符号)。在char
和int
之间转换时,char
会被提升为int
,而int
被转换为char
时,只保留低8位。
30. static的用法和定义是什么?
- 用法:
- 静态局部变量:函数内部的
static
变量在函数调用结束后依然存在,并保留其值。 - 静态全局变量:文件内的
static
变量或函数仅在定义所在的文件中可见。 - 静态类成员:类的
static
成员属于类而不属于具体对象,所有对象共享该成员。
- 静态局部变量:函数内部的
31. const常量和#define的区别是什么?
- 区别:
const
:编译时常量,有类型检查,可以用在调试和类型安全中。#define
:预处理器宏,简单的文本替换,没有类型检查,也不会占用内存。
32. volatile的作用和用法是什么?
- 作用:告诉编译器变量可能在程序外部(如硬件或其他线程)修改,避免编译器优化时省略对该变量的访问。
- 用法:在多线程编程、内存映射I/O或中断处理程序中使用。
33. 为什么没有引用常量?
- 原因:引用必须绑定到一个对象,而常量在定义时未必存在具体的对象。引用常量没有太大意义,因为引用本质上是一个别名。
34. C/C++中变量的作用域是什么?
- 局部作用域:在函数或代码块内声明的变量,仅在该函数或代码块内可见。
- 全局作用域:在所有函数外部声明的变量,在整个文件内或其他文件(通过
extern
)中可见。 - 类作用域:类内定义的成员变量和成员函数,仅在类内部或通过类对象访问。
35. C++中类型转换机制有哪些?dynamic_cast转换失败时会出现什么情况?
- 类型转换机制:
- static_cast:静态类型转换,用于相关类型之间的转换(如基本类型)。
- dynamic_cast:用于将基类指针或引用转换为派生类指针或引用,要求有虚函数表(RTTI)。
- const_cast:用于去掉或增加
const
限定符。 - reinterpret_cast:低级别的强制转换,用于指针类型之间的转换。
- dynamic_cast转换失败时:
- 如果转换的是指针,返回
nullptr
。 - 如果转换的是引用,抛出
std::bad_cast
异常。
- 如果转换的是指针,返回