文章目录
- 索引
- 一 基本概念
-
- 1 const、define
- 2 static
- 3 volatile、mutable和explicit关键字的用法
- 4 assert
- 5 sizeof、strlen
- 6 位域、补码、反码、位运算
- 7 union 联合
- 8 C++ 中 struct 和 class
- 9 struct 和 typedef struct
- 10 using
- 11 范围解析运算符
- 12 enum 枚举类型
- 13 decltype
- 14 引用、指针
- 15 区别以下指针类型?
- 16 IPC
- 17 内存结构布局
- 18 auto
- 19 lambda
- 20 atomic && thread_local
- 21 C++的异常处理的方法
- 22 C++ | 一文整理Effective C++ 55条款内容(全)
- 23 More Effective C++
- 24 C和C++的类型安全
- 25 浅拷贝和深拷贝的区别
- 26 代码判断大小端
- 27 字节、十六进制、二进制之间的关系
- 28 用户态与内核态的切换
- 29 怎样判断两个浮点数是否相等?
- 30 C++出现访问越界
- 31 C++和C的区别
- 32 局部变量存储在哪
- 33 早绑定和晚绑定
- 34 编写一个C/C++需要注意一些什么
- 35 do……while(0)
- 三、线程、进程、协程
索引
【复习整理归纳】| C++面经(基础概念、线程、进程)
【复习整理归纳】| C++面经(函数相关)
【复习整理归纳】| C++面经(内存管理)
【复习整理归纳】| C++面经(STL及项目)
一 基本概念
1 const、define
作用:修饰的变量不能再作为左值,初始化后,值不能被修改;
修饰变量,该变量不可以被改变;
修饰指针,分为指向常量的指针和自身是常量的指针;
修饰引用,指向常量的引用,用于形参类型,即避免了拷贝,又避免了函数对值的修改;
只有常量的引用:因为引用只是对象的别名,不能用 const 修饰;
修饰成员函数,说明该成员函数内不能修改成员变量。
1.1 C和C++中const的区别
C:const是一个伪常量,会分配内存,即当作一个变量来编译生成指令;const为外部链接;
C++:const不会分配内存,所有出现const常量名字的地方,都被常量的初始化替换;const为内部链接;
- const的编译方式不同:
- C++中不能作为左值了;
【注意】当什么时候C++中的const会被分配内存:
取地址时会临时分配内存;
extern时也会分配内存;
将普通变量初始化(赋值)const;
自定义数据类型(使用结构体);
// C++
const int a = 20; // 常量:必须初始化
int b = 10;
const int c = 10; // 常变量:C++中初始值不是立即数,是一个变量
// C
const int a = 10; // 常变量
1.2 C++ const分配内存
// 若按照C语言的方式去尝试修改值的话。在取地址时,编译器会主动开辟一个临时的地址来暂存该值而指针修改的是
// 该临时地址中的值
const int a = 20;
int *p = (int*)&a;
*p = 30;
此时a会产生一个临时变量
1.3 const常见错误
- 常量不能再作为左值;
- 不能把常量的地址泄漏给一个普通的指针或普通的引用变量;
1.4 const和指针的类型转换公式
int* <= const int* 错误
const int* <= int* 可以
int** <= const int** 错误
const int** <= int** 错误
int** <= int* const* 错误
int* const* <= int** 可以
1.5 宏定义 #define 和 const 常量
宏定义 #define const 常量
宏定义,相当于字符替换 常量声明
预处理器处理 编译器处理
无类型安全检查 有类型安全检查
不分配内存 要分配内存
存储在代码段 存储在数据段
可通过 #undef 取消 不可取消
========》【C++】| 如何用好#define《========
2 static
2.1 作用
修饰变量,修改变量的
存储区域
和生命周期
:
- 使局部变量从
栈区
存储到.data或.bss
,产生了符号;- 使全局变量的符号表中,符号的作用域从
g
变成了l
;- 在 main 函数运行
前就分配了空间
;- 如果有初始值就用初始值初始化它,如果没有初始值系统用
默认值初始化
它。
修饰普通函数,表明函数的
作用范围
:
- 仅在定义该函数的
文件内
才能使用,防止与他人命名空间里的函数重名
;
修饰成员变量,修饰成员变量使
所有的对象
只保存一个该变量
,而且不需要生成对象就可以访问该成员
;
修饰成员函数,修饰成员函数使得
不需要生成对象就可以访问该函数
,但是在 static 函数内不能访问非静态成员
;
2.2 static为什么能保留上一次的值
函数结束时,静态局部变量不会消失,每次该函数调用 时,也不会为其重新分配空间。它始终驻留在
全局数据区
,直到程序运行结束;
2.3 const和static的区别
面向过程:
- const:修饰全局、局部、形参变量,不能修饰函数
- static:修饰全局、局部变量,可以修饰函数
面向对象:
- 常方法,常成员变量;
- 静态方法,静态成员变量;
3 volatile、mutable和explicit关键字的用法
3.1 volatile
========》可参考《========
- 表示可被某些编译器未知因素更改;
- 声明一个变量时,系统总会重新从他所在的内存中读取数据,即使前面的指令读取过数据;避免多个寄存器中有多个备份,在多线程中出现线程安全的问题;
- 即防止优化编译器把变量从内存转入寄存器;进行运算,运算好没有及时将运算结果写回到内存中,就被另外一个线程抢占,而该线程继续从内存中取出放入寄存器中执行运算,故两次操作只会出现一次生效;
- 该关键字类似const,可修饰指针;
- 可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。
- 除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
- C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。
3.2 mutable
- 为了突破const限制,为可变状态,可在常函数中被修改;
3.3 explicit
- explicit 修饰构造函数时,可以防止
隐式转换
和复制初始化
; - explicit 修饰转换函数时,可以防止
隐式转换
,但 按语境转换 除外;
struct A
{
A(int) { }
operator bool() const { return true; }
};
struct B
{
explicit B(int) {}
explicit operator bool() const { return true; }
};
void doA(A a) {}
void doB(B b) {}
int main()
{
A a1(1); // OK:直接初始化
A a2 = 1; // OK:复制初始化
A a3{ 1 }; // OK:直接列表初始化
A a4 = { 1 }; // OK:复制列表初始化
A a5 = (A)1; // OK:允许 static_cast 的显式转换
doA(1); // OK:允许从 int 到 A 的隐式转换
if (a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a6(a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a7 = a1; // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a8 = static_cast<bool>(a1); // OK :static_cast 进行直接初始化
B b1(1); // OK:直接初始化
B b2 = 1; // 错误:被 explicit 修饰构造函数的对象不可以复制初始化
B b3{ 1 }; // OK:直接列表初始化
B b4 = { 1 }; // 错误:被 explicit 修饰构造函数的对象不可以复制列表初始化
B b5 = (B)1; // OK:允许 static_cast 的显式转换
doB(1); // 错误:被 explicit 修饰构造函数的对象不可以从 int 到 B 的隐式转换
if (b1); // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
bool b6(b1); // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
bool b7 = b1; // 错误:被 explicit 修饰转换函数 B::operator bool() 的对象不可以隐式转换
bool b8 = static_cast<bool>(b1); // OK:static_cast 进行直接初始化
return 0;
}
4 assert
其作用是如果它的
条件返回错误
,则终止程序执行
;
- 可以通过定义 NDEBUG 来关闭 assert,但是需要在源代码的开头,include <assert.h> 之前;
#define NDEBUG // 加上这行,则 assert 不可用
#include <assert.h>
assert( p != NULL ); // assert 不可用
5 sizeof、strlen
sizeof 对数组,得到整个数组所占空间大小;
sizeof 对指针,得到指针本身所占空间大小;
5.1 sizeof与strlen的区别
- strlen 是
函数
,sizeof 是运算符
;- strlen 测量的是
字符的实际长度
,以'\0' 结束
不包含\0
;而sizeof 测量的是字符的分配大小
包括\0
;- 子函数中,
sizeof
会把从主函数中传进来的字符数组
当作是指针
来处理;- strlen
运行时
,而大部分编译程序在编译就把sizeof计算过了是类型或是变量的长度;- sizeof可以用
类型
做参数,strlen只能用char*
做参数,且必须是以’‘\0’'结尾的;- 不应用sizeof去获取动态内存的长度;
int main() {
char arr[129] = "123";
cout << sizeof(arr) << endl; // ret: 129
cout << sizeof(arr)/sizeof(arr[0]) << endl; // ret: 129
cout << strlen(arr) << endl; // ret: 3
char* arr2 = (char*)"123";
cout << sizeof(arr2) << endl; // ret: 4
cout << sizeof(arr2) / sizeof(arr2[0]) << endl; // ret: 4
cout << strlen(arr2) << endl; // ret: 3
}
6 位域、补码、反码、位运算
类可以将其(非静态)数据成员定义为位域,在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域;
- 位域在内存中的布局是与
机器
有关的; - 位域的类型必须是
整型或枚举
类型,带符号类型中的位域的行为将因具体实现而定; - 取地址运算符(&)不能作用于位域,任何指针都无法指向类的位域;
正数:源码、反码、补码相同;
负数:
- 反码等于源码除符号位外,其他位求反;
- 补码等于反码+1;
7 union 联合
是一种
节省空间
的特殊的类,一个 union 可以有多个数据成员
,但是在任意时刻只有一个数据成员可以有值
;当某个成员被赋值后其他成员变为未定义状态
;
特点:
- 默认访问控制符为
public
;- 可以含有
构造
函数、析构
函数;不能
含有引用
类型的成员;不能继承
自其他类,不能作为基类;不能
含有虚
函数;- 匿名 union 在定义所在作用域可
直接访问
union 成员;- 匿名 union
不能包含 protected 成员或 private 成员
;全局匿名联合
必须是静态
(static)的;
#include<iostream>
// 有析构、构造、默认为public,其他权限不行,不能有虚函数,不能作为基类,不能继承
union UnionTest {
UnionTest() : i(10) {};
int i;
double d;
};
// 全局匿名只能为静态
static union {
int i;
double d;
};
int main() {
UnionTest u;
// 匿名
union {
int i;
double d;
};
std::cout << u.i << std::endl; // 输出 UnionTest 联合的 10
::i = 20;
std::cout << ::i << std::endl; // 输出全局静态匿名联合的 20
i = 30;
std::cout << i << std::endl; // 输出局部匿名联合的 30
return 0;
}
8 C++ 中 struct 和 class
总的来说,struct 更适合看成是一个
数据结构
的实现体,class 更适合看成是一个对象
的实现体;
区别
- 默认的继承访问权限。struct 是
public
的,class 是private
的;
9 struct 和 typedef struct
9.1 C中
// c
typedef struct Student {
int age;
} S;
// 等价
struct Student {
int age;
};
typedef struct Student S;
# 此时 S 等价于 struct Student,但两个标识符名称空间不相同。
# 另外还可以定义与 struct Student 不冲突的 void Student() {}。
9.2 C++
编译器定位符号的规则(
搜索规则
)改变,导致不同于C语言。
一、如果在类标识符空间定义了 struct Student {...};,使用 Student me; 时,编译器将搜索全局标识符表,
Student 未找到,则在类标识符内搜索;
struct Student {
int age;
};
void f( Student me ); // 正确,"struct" 关键字可省略
二、若定义了与 Student 同名函数之后,则 Student 只代表函数,不代表结构体,如下:
typedef struct Student {
int age;
} S;
void Student() {} // 正确,定义后 "Student" 只代表此函数
//void S() {} // 错误,符号 "S" 已经被定义为一个 "struct Student" 的别名
int main() {
Student();
struct Student me; // 或者 "S me";
return 0;
}
9.3 C和C++的区别
- C语言中:struct是用户自定义数据类型(UDT);C++中struct是抽象数据类型(ADT),支持成员函数的定义,(C++中的struct能继承,能实现多态)
- C中struct是没有权限的设置的,且struct中只能是一些变量的集合体,可以封装数据却不可以隐藏数据,而且成员不可以是函数
- C++中,struct增加了访问权限,且可以和类一样有成员函数,成员默认访问说明符为public(为了与C兼容)
- struct作为类的一种特例是用来自定义数据结构的。一个结构标记声明后,在C中必须在结构标记前加上struct,才能做结构类型名;C++中结构体标记(结构体名)可以直接作为结构体类型名使用,此外结构体struct在C++中被当作类的一种特例;
9.4 struct 内存对齐问题?
- 第一个成员的地址和结构地址相同;
- sizeof()按最大字节数成员对齐;
- alignas对齐调整但对单字节无效,alignof获取对齐字节数;
- 单字节对齐
#pragma pack(push,1)
,#pragma pack(pop)
; - 可在元素后加上元素大小;
10 using
10.1 using 声明
一条 using 声明 语句
一次只引入命名空间的一个成员
;它使得我们可以清楚知道程序中所引用的到底是哪个名字;
using namespace_name::name;
10.2 构造函数的 using 声明
在 C++11 中,
派生类
能够重用其直接基类
定义的构造函数;
class Derived : Base {
public:
using Base::Base;
/* ... */
};
# 对于基类的每个构造函数,编译器都生成一个与之对应(形参列表完全相同)的派生类构造函数
10.3 using 指示
using 指示 使得某个特定命名空间中所有名字都可见,这样我们就无需再为它们添加任何前缀限定符了;
using namespace_name name;
10.4 using 使用
// 尽量少使用 using 指示污染命名空间
using namespace std;
// 应该多使用 using 声明
int x;
std::cin >> x ;
std::cout << x << std::endl;
//或者
using std::cin;
using std::cout;
using std::endl;
int x;
cin >> x;
cout << x << endl;
11 范围解析运算符
11.1 分类
- 全局作用域符(::name):用于类型名称(类、类成员、成员函数、变量等)前,表示作用域为全局命名空间;
- 类作用域符(class::name):用于表示指定类型的作用域范围是具体某个类的;
- 命名空间作用域符(namespace::name):用于表示指定类型的作用域范围是具体某个命名空间的;
int count = 11; // 全局(::)的 count
class A {
public:
static int count; // 类 A 的 count(A::count)
};
int A::count = 21;
void fun()
{
int count = 31; // 初始化局部的 count 为 31
count = 32; // 设置局部的 count 的值为 32
}
int main() {
::count = 12; // 测试 1:设置全局的 count 的值为 12
A::count = 22; // 测试 2:设置类 A 的 count 为 22
fun(); // 测试 3
return 0;
}
12 enum 枚举类型
- 强作用域,不会被输出到父作用域;
- 转化限制,不能被隐式转换,比较需要通过显示转换;
- 可以指定底层类型,在枚举名称后加上
: type
,type不能为wchar_t;
12.1 限定作用域的枚举类型
enum class open_modes { input, output, append };
12.2 不限定作用域的枚举类型
enum color { red, yellow, green };
enum { floatPrec = 6, doublePrec = 10 };
13 decltype
- decltype 关键字用于
检查实体的声明类型
或表达式的类型
及值
分类;
// 尾置返回允许我们在参数列表之后声明返回类型
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
// 处理序列
return *beg; // 返回序列中一个元素的引用
}
// 为了使用模板参数成员,必须用 typename
template <typename It>
auto fcn2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type
{
// 处理序列
return *beg; // 返回序列中一个元素的拷贝
}
14 引用、指针
========》【C++11新特性】| 【02】继承、委派构造、右值引用及其他类型《========
14.1 左值引用
- 常规引用,一般表示对象的身份;
14.2 右值引用
- 一个
临时对象
的引用;int &&c = 20;
专门用来引用右值类型,指令上,可自动产生临时量然后直接引用临时量
;- 本身是一个左值,只能用
左值引用
来引用它;
右值引用可实现转移语义和精确传递,它的主要目的有两个方面:
- 消除两个对象交互时
不必要的对象拷贝
,节省运算存储资源,提高效率;- 能够更简洁明确地定义泛型函数;
14.3 指针和引用
- 引用是一种
更安全的指针
,需要被初始化
,不能为空,初始化后引用关系不能被改变;- 引用一般用于传参、返回值居多;
- 不要返回局部变量引用;
- 引用只有一级引用,指针可以多级;
- 定义一个引用变量,和定义一个指针变量,其汇编指令是一样的;通过引用变量修改所引用内存的值,和通过指针解引用修改指针下指向的内存的值,其底层的指令也是一样的;通过lea取到对应的地址,在通过mov将其数据移动到相应的内存;
- 引用和指针都有占用空间,大小一样;
14.4 一级指针
// 在内存0x0018ff44写一个4字节的10
int *const &p = (int*)0x0018ff44;
*p = 10;
14.5 const和一级指针结合
- const修饰离它最近的类型;
1.const int *p = &a;
2.int const* p;
1.2可以任意指向不同的int类型的内存,但不能通过指针间接修改指向的内存的值
3.int *const p = &a;
指针p为常量,不能指向它的内存,但可通过解引用修改指向内存的值;
4.const int *const p;
结合上述两种情况
- const若右边没有指针*,则const是不参与类型的;
int *q1 = nullptr;
int *const q2 = nullptr;
// q1的类型为int*
// const再右边没有*故不参与类型,类型为int*
14.6 二级指针
14.6 const和二级指针结合
const int **q; // *q和q可被赋值
int *const *q; // **q和q可被赋值
int **const q; // **q和*q可被赋值
15 区别以下指针类型?
- 数组指针:int (*p)[10]一个数组变量;
- 指针数组:char* str[10]一个数组变量,10个char*;
- 函数指针:int (*p)();
- 函数声明:int *p(int);
16 IPC
【进程间通信机制】:
- 信号:用来表示事件的发生;
- 管道:和FIFO,用于在进程间传递数据;
- 套接字:供同一台主机或是联网的不同主机上所运行的进程之间传递数据;
- 文件锁定:为防止其他进程读取或更新文件内容,允许某进程对文件的部分区域加以锁定;
- 消息队列:用于在进程间交拖消息(数据包);
- 信号量:用来同步进程动作;
- 共享内存:允许两个及两个以上进程共享一块内存;
17 内存结构布局
18 auto
19 lambda
20 atomic && thread_local
https://blog.csdn.net/weixin_45926547/article/details/124737526
21 C++的异常处理的方法
try、throw、catch
- 先执行try语句若出现错误,throw抛出语句,在使用catch进行捕获;可通过throw抛出通知上一层;
exception
- **bad_typeid**:若操作数时一个多态类的指针,若为NULL,会抛出;
- **bad_cacst**:使用dymanic进行转换从基类到派生类的转换,若是不安全,则会抛出异常;
- **bad_alloc**:使用new没有分配到足够的内存;
- **out_of_range**:下标越界;
22 C++ | 一文整理Effective C++ 55条款内容(全)
========》C++ | 一文整理Effective C++ 55条款内容(全)《========
23 More Effective C++
========》C++ | 【01 基础提议】More Effective C++《========
24 C和C++的类型安全
- 类型安全:相等于内存安全,安全的代码不会访问被非法内存,标准在于该程序是否隐含类型错误;
- C的类型安全:只在局部上下文中表现出类型安全,如结构体指针转换(强制转换),编译器报错;但还是有大多数不是类型安全,比如int和float转换;
- C++:比C更有安全性,如new、inline;dynamic_cast;
25 浅拷贝和深拷贝的区别
- 浅拷贝:只拷贝指针,没有开辟地址,和原来的指向同一个地址,若原来的释放了可能会出现错误;
- 拷贝值和分配新的地址,即使原先的被释放了,也不会被影响;
26 代码判断大小端
/** 查看当前系统使用大端还是小端 */
#include <iostream>
using namespace std;
void test() {
14 union {
15 short s;
16 char c[sizeof(short)];
17 }un;
18 /*
19 * short为两字节
20 * 0x0102:
21 * 若为大端:0x0102
22 * 若为小端:0x0201
23 * */
24 un.s = 0x0102; // 查看它的两个连续字节c[0]和c[1]的地址,来确定字节序
25 if(sizeof(short) == 2) {
26 if(un.c[0] == 1 && un.c[1] == 2)
27 cout << "big-endian" << endl;
28 else if(un.c[0] == 2 && un.c[1] == 1)
29 cout << "little-endian" << endl;
30 else
31 cout << "unknown" << endl;
32 }else {
33 cout << "sizeof(short) = " << sizeof(short) << endl;
34 }
35 exit(0);
36 }
37
38 int main(int argc, char* argv[])
39 {
40 test();
41
42 return 0;
43 }
27 字节、十六进制、二进制之间的关系
一个16进制对应4个二进制位(1248),2个16进制对应8个二进制位,以及一个字节;
2个16进制位 == 一个字节;
优缺点:
- 小端模式 :强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样。
- 大端模式 :符号位的判定固定为第一个字节,容易判断正负。
28 用户态与内核态的切换
- 保留用户态现场(
上下文、寄存器、用户栈
);复制用户态参数
,用户栈切到内核栈,进入内核态;额外的检查
;执行
内核态代码;- 复制内核态代码执行
结果
,回到用户态;恢复
用户态现场(上下文、寄存器、用户栈);
29 怎样判断两个浮点数是否相等?
- 不能直接使用==;
- 通过相减在与相应的精度比较;
30 C++出现访问越界
- 在使用数组或容器、string的时候在随机时出现越界;
- 字符串处理越界,没有添加’\0’;
- 使用类型强制转换时,当一个较大的内存块的指针指向一个较小的内存块,对他的内存进行访问;
31 C++和C的区别
- 引用
- 函数重载
- new/delete
- const、intline
- 模板编程
- 类对象
- STL
- 异常
- 智能指针
- 运算符重载
32 局部变量存储在哪
- 存储在栈中,通过ebp指针的便宜来获取,不会产生符号,存储在.text段中;
33 早绑定和晚绑定
- 早绑定:普通函数的调用,用对象调用虚函数在编译阶段就知道调用哪个函数;
- 晚绑定:用指针或引用调用虚函数,通过动态绑定在运行时;
34 编写一个C/C++需要注意一些什么
- 先分析需求,在进行概要设计和详细设计看一下有哪些功能点需要实现,功能点可以分成哪些模块,模块与模块之间是否有哪些共性;
- 是否需要在多线下运行,是否需要考虑线程安全、内存泄漏、需要使用什么设计模式
35 do……while(0)
三、线程、进程、协程
1 协程
2 进程相关
3 虚拟内存管理
4 栈和栈帧
5 进程与线程的区别与联系
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程;
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源;
- 不同进程的线程间要利用
消息通信
的办法实现同步;- 划分尺度:线程更小,所以多线程程序并发性更高;
- 地址空间:进程拥有独立的地址空间,同一进程内多个线程共享其资源;
- 执行:每个线程都有一个程序运行的入口,顺序执行序列和程序的出口,但线程不能单独执行,必须组成进程;
5.1 开销
fork在创建的时机需要复制许多
内存页表
和文件描述符表
等进程间的属性,而由于线程是共享机制
,在创建是无需进行复制信息;
5.2 稳定性
当一个
进程崩溃后
,在保护模式下不会对其他进程产生影响
,而当线程崩溃时会导致整个进程崩溃,由于所有线程共享进程的内存,故多进程比多线程更加稳定;
5.3 进程切换与线程切换的区别
进程切换与线程切换的一个最主要区别就在于
进程切换涉及到虚拟地址空间的切换
而线程切换则不会。因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的
,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。
页表查找速度慢
;- cache用来缓冲常用
地址映射
,加速页表;- 进程切换会导致cache失效;
6 join和detach的区别
- join:
主线程
需要用join()函数来等待子线程执行完毕获得返回值
,并释放资源
;- detched:分离的线程,主线程结束,整个进程结束;
- joinable 的线程可以转换成 detached 的线程,但不能反向转换;
7 条件变量、互斥量
7.1 条件变量
- 需要结合
互斥量
使用,来保证条件变量传入前不会被改动,故在调用pthread_cond_wait时,需要先进行lock(),而在wait内部有进行对互斥量解锁操作,当条件变量被唤醒时,互斥量会在次上锁,保护条件变量,故在wait后需要在unlock();
- 条件变量需要注意一个点,pthread_cond_wait()需要
放在循环体
中,由于条件变量可能会出现虚假唤醒
;
避免当任务队列为空时,条件变量被唤醒,做无意义动作;
7.2 互斥量
========》Linux | Linux中的线程、互斥量、信号量的基本使用《========
========》【C++模块实现】| 【07】对于互斥、自旋锁、条件变量、信号量简介及封装《========
7.3 信号量
========》Linux | Linux使用互斥锁及条件变量替代信号量《========
8 如何避免死锁
最简单有效的方法就是
定义互斥量的层级关系
,当多个线程不能总是先锁定mutex1,在锁定mutex2,交叉互换即可;
9 thread_local
- 对象的存储在线程开始时分配,而在线程结束时解分配;
- 能与 static、extern一同出现;
10 线程
========》Linux | Linux中的线程、互斥量、信号量的基本使用《========
========》【C++模块实现】| 【09】线程模块及线程池的实现《========
CPU从程序计数器(PC)中获取指令;
程序计数器:存放CPU要执行的下一条指令;
私有:栈区、栈指针、程序计数器、函数运行时使用的寄存器、线程局部存储;
10.1 共享了哪些资源
- 代码区:存放编译后的指令,是从可执行文件中加载到内存;
- 数据区:存放全局变量、静态变量;
- 堆区::new、malloc
- 栈区:是私有数据,但没有提供保护措施,可以修改;
- 动态链接库:
- 文件
10.2 谁来设置PC寄存器中的指令地址呢?
PC寄存器中的地址默认是自动加1的,当遇到条件等指令会动态改变PC寄存器的值;
10.3 指令从哪里来
源文件通过编译生成可执行文件,指令从磁盘中加载可执行文件;
10.4 函数
CPU执行指令,只需要找到函数的对应第一条指令(函数入口)写入PC寄存器即可;
10.5 没有操作系统我们也可以让CPU执行程序
- 在内存中找到一块大小合适的区域装入程序
- 找到函数入口,设置好PC寄存器让CPU开始执行程序
10.6 进程
- 在内核栈中用来保存一个进程在内核中的内存区域,需要保存函数入口地址,内存的起始地址,长度等等;
- 线程是运行在进程的地址空间上;
- 地址空间相互隔离,通信较难,开销大;
11 如何确定线程池中线程的数量
11.1 长任务
如打开一个文件进行编辑;
11.2 短任务
请求、连接、查询
11.3 CPU密集型
说处理任务不需要
依赖外部I/O
,比如科学计算、矩阵运算等等。在这种情况下只要线程的数量和核数基本相同
就可以充分利用CPU资源;
11.4 I/O密集型
这一类任务可能计算部分所占用时间不多,大部分时间都用在了比如
磁盘I/O、网络I/O
等;
大概需要2N核线程
,【N * (1 + WT/CT)】
WT:为I/O等待的时间;
CT:CPU所需的时间;
11.5 线程增多时需要考虑哪些
内存占用、系统调度、打开的文件数量、打开的socket数量以及打开的数据库链接;
13 回调函数
类似模板方法,A生成一个库,B将其调用,但内部执行函数由B来指定;
15 虚拟内存
虚拟内存是操作系统为每个进程提供的一种抽象
,每个进程都有属于私有的、地址连续
的虚拟内存,当然我们知道最终进程的数据及代码必然要放到物理内存
上,那么操作系统是如何记住这种映射关系的呢,答案就是页表
,页表中记录了虚拟内存地址到物理内存地址的映射关系
;有了页表就可以将虚拟地址转换为物理内存地址了,这种机制就是虚拟内存;
- 每个进程都有自己的虚拟地址空间,进程内的所有线程共享进程的虚拟地址空间。
地址空间映射
:也就是虚拟内存地址与物理内存地址的映射关系;
16 C++11 锁
unique_lock<std::mutex>:可以在简单的临界区中使用,也可以进行函数传递;提供右值引用;
lock_guaid:只不能用于函数传递或返回;