# SW Development Skill
# 1. Makefile
# 1.1 Makefile 简介
用途:
- 描述整个工程的编译,链接的规则
- 软件项目的自动化编译
工作过程:
- 解析 Makefile 命令,建立依赖关系图
- 命令运行阶段
# 1.2 Makefile 中的概念
命令基本构成:
- 目标
- 目标依赖
- 命令
#目标:目标依赖 | |
#[tab] 命令 | |
main: a.o | |
gcc -o main a.o |
Tip:
- 命令开头必须摇头 Tab
- 一个规则可以没有目标依赖,仅仅描述某种操作
- 一个规则可以没有命令,仅仅描述依赖关系
- 一个规则必须有一个目标
目标:
默认目标
- 一个 Makefile 里面可以有多个目标
- 一般选择第一个默认目标
多目标
- 一个规则中可以有多个目标
- 多个目标具有相同的生成命令和依赖文件
多规则目标
- 多个规则可以是同一个目标
- Make 在解析中,会将多个规则的依赖文件合并
伪目标
- 并不是一个真正的文件名,可以看成是一个标签,用
.PONHY
申明 - 无依赖,相比一般文件不会去重新生成和执行
- 可以无条件执行
- 并不是一个真正的文件名,可以看成是一个标签,用
文件时间戳:
- 根据时间戳来判断目标依赖文件是否更新
- 若所有文件没有编译过,则对所有文件编译,生成可执行程序
- 在上次 make 之后修改过的
.c
文件,会被重新编译 - 在上次 make 之后修改过的
.h
文件,依赖此头文件的会被重新编译
自动产生依赖:
GCC -M
自动生成该文件要依赖的文件
命令:
- 每一个命令,make 都会开一个进程
- 每条命令执行完,make 会检查返回的状态码
- 返回成功:继续下一个命令
- 返回失败:终止当前规则退出
# 1.3 Makefile 的语法
变量:
#定义: | |
CC = gcc | |
#赋值: | |
#追加赋值: += | |
#条件赋值: ?= | |
#变量引用 | |
$(CC) ${CC} | |
#立即展开变量: 在解析阶段 直接赋值 常量字符串 (一般用在目标依赖) | |
ARCH := 'arm64' | |
#延迟展开变量: 在运行阶段 实际使用变量时 再进行求值 (一般用在命令中) | |
ARCH = 'arm64' | |
#自动变量 目标 $@ 所有目标依赖 $^ 第一个依赖 $< | |
gcc -o $@ $^ | |
#系统环境变量 | |
#CFLAG | |
#SHELL | |
#MAKE | |
#对所有 makefile 都有效,在 make 开始运行时被载入到 Makefile 文件中。若 Makfile 中定义 | |
#了同名变量,将会被覆盖,在命令行中传递同名,同样被覆盖。 | |
#变量传递 多目录下递归执行 | |
$(MAKE) -c subdir | |
cd subdir && (MAKE) | |
#通过 export 传递 | |
#通过命令行传递 |
条件执行:
ifeq else endif
ifneq
条件语句从
ifeq
开始,括号和关键字用空格隔开
函数:
常用函数:
#打印 warning 信息 | |
#返回: 打印的 & lt;text...> 变量 | |
$(warning <text...>) | |
#打印 error 信息 | |
#返回: 打印的 & lt;text...> 变量,并且 exit | |
$(error <text...>) |
# 1.4 依赖关系树
依赖关系树的生命周期:
- 解析阶段载入内存
- 运行阶段根据其进行编译,根据时间戳生成文件
- 新文件添加,减少会动态改变依赖关系树
# 1.5 执行过程
- 进入编译目录
- 执行 make
- 依赖关系解析
- 解析 Makefile 建立依赖关系图
- 控制解析过程:引入 Makefile,变量展开,条件执行
- 生成依赖关系树
- 命令执行阶段
- 将解析生成的依赖关系树加载到内存
- 依照依赖关系,按顺序生成这些文件
- 再次编译 Make 会检查文件的时间戳,判断是否过期
- 若无过期,退出
- 若有更新,则依赖该文件的所有依赖关系上的目标重新更新,编译生成
状态码:
0: 成功执行
1: 运行错误,返回 1
# 1.6 隐含规则
规则:
默认
.c
编译生成对应的.o
目标文件取消隐含规则: 使用
-r
或者-R
eg:
make -r
命令变量:
- CC:编译程序,默认 cc
- AS:汇编程序,默认 as
- CXX:C 编译程序,默认 g
- AR:函数库打包程序,默认是 ar
命令参数变量:
- CFLAGS:执行 CC 编译器的命令参数
- CXXFLAGS:执行 g++ 编译器的命令参数
- ASFLAGS:执行 as 编译器的命令参数
- ARFLAGS:执行 ar 编译器的命令参数
# 1.7 代码示例
LOCAL_PATH:=$(call my-dir) #定义模块当前路径 | |
include $(CLEAR_VARS) #清空当前环境变量 | |
LOCAL_xxx :=xxx #引入头文件 | |
LOCAL_MODULE :=hello #编译生成的文件名 | |
LOCAL_SRC_FILES :=hello.c #编译需要的源码 | |
$(BUILD_EXECUTABLE) | |
#编译生成文件的类型 | |
#LOCAL_MODULE_CLASS,JAVA_LIBRARIES | |
#APPS,SHARED_LIBRARIES | |
#EXECUTABLES,ETCinclude |
编译动态库
LOCAL_PATH:=$(call my-dir) #定义模块当前路径 | |
include $(CLEAR_VARS) #清空当前环境变量 | |
LOCAL_MODULE :=libhello #编译生成的文件名 | |
LOCAL_CFLAGS = $(L_CFLAGS) | |
LOCAL_SRC_FILES = hello.c | |
LOCAL_C_INCLUDES = $(INCLUDES) | |
LOCAL_SHARED_LIBRARIES := libcutils | |
LOCAL_COPY_HEADERS_TO := libhello | |
LOCAL_COPY_HEADERS := hello.h | |
#编译动态库 BUILD_SHARED_LIBRARY | |
include $(BUILD_SHARED_LIBRARY) |
编译静态库
LOCAL_PATH:=$(call my-dir) #定义模块当前路径 | |
include $(CLEAR_VARS) #清空当前环境变量 | |
LOCAL_MODULE :=libhello #编译生成的文件名 | |
LOCAL_CFLAGS = $(L_CFLAGS) | |
LOCAL_SRC_FILES = hello.c | |
LOCAL_C_INCLUDES = $(INCLUDES) | |
LOCAL_SHARED_LIBRARIES := libcutils #这一行不知有么有错误 | |
LOCAL_COPY_HEADERS_TO := libhello | |
LOCAL_COPY_HEADERS := hello.h | |
#编译动态库 BUILD_STATIC_LIBRARY | |
include $(BUILD_STATIC_LIBRARY) |
引用库
#引用静态库 | |
LOCAL_STATIC_LIBRAIES += libxxxx | |
LOCAL_STATIC_LIBRAIES := \ | |
libxxx2\ | |
libxxx \ | |
#引用动态库 | |
LOCAL_SHARED_LIBRARIES += libxxx | |
LOCAL_SHARED_LIBRARIES := liblog libnativehelper libGLESv2 | |
#引用第三方库文件 | |
LOCAL_LDFLAGS := -L/PATH-Lxxx | |
LOCAL_LDFLAGS := $(LOCAL_PATH)/lib/libtest.a | |
#引用第三方头文件 | |
LOCAL_C_INCLUDE := $(INCLUDES) |
# 2. C Advance
Source code(.cpp .c .h) | |
| | |
| eg: gcc -E test.c -o test.i | |
V Step: 1:preprocessor (cpp) | |
Preprocesing | |
| | |
include header,Expand Macro | eg: gcc -S -fno-builtin test.i -o test.s | |
V Step: 2:Compiler (gcc,g++) | |
Compilation | |
| | |
Assembly Code .S | eg: gcc -c test.s -o test.o | |
V Step: 3:Assembly (as) | |
Assemble | |
| | |
Machine Code (.o .obj) | eg: gcc test.o -o test | |
V Step: 4:Linker(ld) | |
Linking | |
| | |
V | |
Executable Machine Code (.exe) |
# 2.1 可执行文件结构
段 | 作用 |
---|---|
.text | 存放可执行的代码指令 和常量 只读属性 防止程序被意外篡改 大小在运行前已经确定 |
.data | 程序中明确被初始化的全局变量,静态变量(全局 / 局部静态变量) |
.bss | 未初始化全局变量和未初始化静态变量 (或者初始化为 0) |
init / Table |
file1.obj | |
----------- | |
| | | |
| .bss | | |
----------- | |
| | Executable objecy moudele Memory Map | |
| .text |----- ------------ ----------- | |
----------- | | .bss(f1) | | | | |
| | | | .bss(f2) | | | | |
| .data |---------- ------------ ----------- | |
------------ | | .data(f1)| | | | |
| | | | .data(f2)| | | | |
| init |------------ ------------ ----------- | |
------------ | | .text(f1)| | | | |
| | .text(f2)| | | | |
| ------------ ----------- | |
file2.obj | | init | | | | |
----------- | | Tables | ----------- | |
| | | ------------ | | | |
| .bss |---------- ----------- | |
----------- | | |
| | | | |
| .text |----- | |
----------- | |
| | | |
| .data | | |
------------ | |
| Tables | | |
| | | |
------------ |
# 2.2 进程的结构
high addr ____________________ | |
| command-line arg | | |
|and env variables | | |
____________________ | |
| stack | | |
-------------------- | |
| | | | |
| V | | |
| | | |
| | | |
| | | |
| | | |
| ^ | | |
| | | | |
-------------------- | |
| heap | | |
____________________ | |
| .bss | ---->initialized to zero by exec | |
____________________ | |
| initialized data | --- | |
____________________ |---> read from program file by exec | |
| text | --- | |
low addr ____________________ |
# 2.3 宏函数
#define SQR(x) x*x | |
// SQR(2) 2*2 = 4 | |
// SQR(2+1) 2+1*2+1 =5 | |
// 替换法则:直接进行替换而不会行进行计算 | |
#define SQR(x) (x) * (x) | |
// SQR(2+1) (2+1) * (2+1) | |
#define ADD(a,b) (a) + (b) | |
//ADD(1,2) 1+2 = 3 | |
//ADD(1,2) * ADD(2,3) 1 + 2 * 2 + 3 =8 | |
// 一样的问题。所以保证结果必须加多一层括号 | |
#define ADD(a,b) ((a)+(b)) | |
//ADD(1,2) * ADD(2,3) (1 + 2) * (2 + 3) = 15 | |
/* 使用包含多语句防止错误 */ | |
#define _my_lock(test) do { \ | |
if (test->lock_enable) \ | |
pthread_mutex_lock(&pcm_lock);\ | |
PROCESS(test); | |
}while(0) | |
if(...) | |
_my_lock(v); | |
// ARRAY_SIZE | |
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) | |
//container_of // 内存对齐 | |
struct test_macro{ | |
int a; //addr:0x84046b0 | |
char b; //addr:0x84046b4 | |
void *c; //addr:0x84046b8 | |
int *c; //addr:0x84046c0 | |
} | |
#define offsetof(type,member) ((size_t) & (type*)0->member) // 拿到绝对偏移 | |
#define _container_of(ptr,type,member)({ \ | |
void *_mptr = (void*)(ptr); \ | |
((type*)(_mptr - offsetof(type,member)); | |
}) | |
struct test_macro *test = NULL; | |
#if 1 | |
size_t offset = (size_t)(&test->d); | |
test = (struct test_macro*)((size_t)(ptr) - offset); | |
#else | |
test = _container_of(ptr,struct test_marco,d); | |
#endif |
Tip:
关于
(type*)0
是因为没有一个实例,所以使用一个 NULL 指针,这个指针为了能够引用实例化后的成员,而 NULL 是 0x000000然后就指导
member
就能得到绝对的偏移值。直接使用 (type*)-> member 是错误的没有对象
//list | |
struct list_head{ | |
struct list_head *next, *prev; | |
}; | |
struct student{ | |
int number; | |
int age; | |
char name[30]; | |
struct list_head node; | |
} | |
#define LIST_HEAD_INIT(name) {&(name), &(name)} | |
#define LIST_HEAD(name) \ | |
struct list_head name = LIST_HEAD_INIT(name) | |
#define INIT_LIST_HEAD(ptr) do{ \ | |
(ptr)->next = (ptr); \ | |
(ptr)->prev = (ptr); \ | |
}while (0) | |
#define list_entry(ptr, type, member) \ \*获得list的入口地址*\ | |
{ | |
((type*)((char *)(ptr) - (unsigned long)(&((type *)0)->member)) | |
} | |
#define list_for_each(pos,head) \ \* 常用在循环list*\ | |
for (pos =(head)->next; pos != (head); pos = pos->next) | |
#define list_for_each_safe(pos,n , head) \ | |
for (pos = (head)->next, n = pos->next; pos != (head); \ | |
pos = n, n = pos->next) | |
#define list_for_each_entry(pos, head, member) \ | |
for(pos = list_entry((head)->next),typeof(*pos),member); \ | |
&pos->member != (head); \ | |
pos = list_entry(pos->member.next, typeof(*pos), member) |
内存操作
void swap(void*p1, void*p2, int size) | |
{ | |
char *buf = (char*)alloca(size); | |
memcpy(buf, p1, size); | |
memcpy(p1, p2, size); | |
memcpy(p2, buf, size); | |
} |
# 2.4 Practical
#include <stdio.h> | |
#include <stdlib.h> | |
#define INIT_LIST_HEAD(ptr) do{\ | |
(ptr)->next = (ptr); (ptr)->prev = (ptr) \ | |
}while(0) | |
#define list_entry(ptr, type , member)\ | |
((type *)((char *)(ptr) - (unsigned long)(&((type *)0)-> member))) | |
#define list_for_each(pos,head) \ | |
for (pos = (head)->next, n = pos->next; pos != (head); \ | |
pos = n, n = pos->head) | |
struct list_head{ | |
struct list_head *next, *prev; | |
}; | |
struct myqueue { | |
struct list_head my_list; | |
}; | |
typedef struct my_element { | |
int vale | |
struct list_head node; | |
}my_element; | |
struct myqueue *queue_init(int element_size); | |
int queue_uninit(struct myqueue*q); | |
int queue_empty(struct myqueue*q); | |
int queue_size(struct myqueue*q); | |
int queue_front(struct myqueue*q); | |
int queue_back(struct myqueue*q); | |
int queue_push(struct myqueue*q, int value); | |
int queue_pop(struct myqueue*q); | |
struct myqueue *queue_init(int element_size) | |
{ | |
int size = element_size; | |
struct myqueue *my_init_quenu = (struct myqueue*)malloc(sizeof(struct myqueue)); | |
struct list_head *head = &(my_init_quenu->my_list); | |
if(!head) return -1; | |
INIT_LIST_HEAD(head); | |
my_element *node; | |
for (unsigned int i = 0; i < size ; ++i) { | |
node = (my_element*)malloc(sizeof(my_element)); | |
if (!node) = return -2; | |
node->vale = i; | |
node->node.next = head->next; | |
head->next->prev = &node->node; | |
head->next = &node->node; | |
node->node.prev = head; | |
node = null; | |
} | |
return my_init_quenu; | |
} | |
int queue_uninit(struct myqueue*q) | |
{ | |
struct list_head * lisptr; | |
my_element * node; | |
list_for_each(lisptr, &q->my_list) | |
{ | |
node = list_entry(lisptr, struct my_element ,node); | |
lisptr = lisptr->next; | |
printf("free node \n"); | |
free(node); | |
node(NULL); | |
} | |
q->my_list.next = NULL; | |
q->my_list.prev = NULL; | |
} | |
int queue_empty(struct myqueue*q) | |
{ | |
if(queue_size(q)==0) | |
{ | |
return 1; | |
} | |
else return 0; | |
} | |
int queue_size(struct myqueue*q) | |
{ | |
if(q->my_list.next == q->my_list.prev) return 0; | |
unsigned int size = 1; | |
struct list_head * ptr = &(q->my_list); | |
int const * ptr_addr = q->my_list.prev; | |
while (ptr->next != ptr_addr) | |
{ | |
size++; | |
ptr = ptr->next; | |
} | |
return size; | |
} | |
int queue_front(struct myqueue*q) | |
{ | |
my_element * node = list_entry(q->my_list.next, struct my_element,node); | |
printf("Top value: %d",node->vale); | |
} | |
int queue_back(struct myqueue*q) | |
{ | |
my_element * node = list_entry(q->my_list.prev, struct my_element,node); | |
printf("Rear value: %d",node->vale); | |
} | |
int queue_push(struct myqueue*q, int value) | |
{ | |
my_element * new_node = (my_element*)malloc(sizeof(my_element)); | |
if(!new_node) return -3; | |
new_node->vale = value; | |
new_node->node.next = q->my_list.next; | |
q->my_list.next->prev = &new_node->node; | |
q->my_list.next = &new_node->node; | |
new_node->node.prev = &(q->my_list); | |
return 0; | |
} | |
int queue_pop(struct myqueue*q) | |
{ | |
my_element *node = list_entry(q->my_list.next, struct my_element,node); | |
q->my_list.next->next->prev = q->my_list.next->prev; | |
q->my_list.next->prev->next = q->my_list.next->next; | |
free(node); | |
node = NULL; | |
return 0; | |
} |
# 3. C++ Advance
# 3.1 函数相关
引用和指针
引用本质上也是使用指针实现的,但是两者还是有许多不同。
- 引用初始化必须赋值
- 引用使用更安全
void duplicate_ (int a) | |
{ | |
a*=2; | |
} | |
void duplicate (int& a) | |
{ | |
a*=2; | |
} | |
int x=1; | |
int y=1; | |
duplicate_(x); | |
duplicate(y); | |
cout << "x=" << x; //1 | |
cout << "y=" << y; //2 |
CONST
- 当函数参数是较复杂数据类型时,值传递存在数据复制,效率较低
- 引用传递效率高,但是有参数值被函数篡改的风险
- 效率高,参数无法被修改
string combine(const string & a, const string &b) | |
{ | |
return a+b; | |
} | |
// 效率高,参数无法被修改 |
缺省参数
能够给函数默认的初值
int divide(int a, int b = 2) | |
{ | |
int r; | |
r = a / b; | |
return r; | |
} | |
cout<< divide(12) //6 | |
cout<< divide(20,4) //5 |
内联函数
函数调用的开销 → 内联函数
效率取向
效率更高,体积更大
无函数调用,而是将代码在调用处展开,与 compiler 有关
inline string combine(const string & a, const string &b) | |
{ | |
return a+b | |
} |
重载
同名不同参函数实现
主要实现的原理是:
主要是通过参数类型来标识函数,所以参数不同函数名同可以,参数同,返回类型不同就不可以
// c++ code | |
float add(float a, float b); | |
double add(float a, float b); //NO | |
// c++ code | |
int add(int a, int b); | |
float add(float a, float b); | |
double add(double a, double b); |
# 3.2 类
# 类的定义
类似 C 语言的 struct
成员不仅有变量,还有函数
- 成员变量
- 成员函数
某些成员只有特定函数能访问
- 公共项 (public)
- 隐藏项 (private)
允许用户重定义功能
class 扩展了基础数据类型
- 类可视为一种数据类型
- 类的实例即为该种数据类型所定义的变量
- 类实例中的成员变量可以是单独拷贝也可以共享
# 构造 / 析构函数
功能
- 成员变量初始化
- 调用
销毁
创建
- 类的实例时,自动调用
类生命周期结束,自动销毁
命名
- 构造函数名与类名相同,且无返回
- 析构函数名和类名相同,但多加一个
~
class Rect{ | |
int width, high; | |
pubilc: | |
Rect(int,int); | |
~Rect(void); | |
int area(){ | |
return (width*heigh); | |
} | |
} | |
Rect::Rect(int a, int b) | |
{ | |
width = a; | |
heigh = b; | |
} |
成员变量初始化
方式一:
Rect::Rect(int a, int b) | |
{ | |
width = a; | |
heigh = b; | |
}; |
方式二:
Rect::Rect(int a, int b):width(a),heigth(b){} |
Tip:
- 若是对基础数据类型,两种方式差别不大
- 若是拓展类型,或者时自定义的类,方式二更高效
class Circle{ | |
double radius; | |
pubilc: | |
Circle(double r): radius(r){} | |
~Circle(void); | |
double area(){return radius * radius * 3.1415} | |
}; | |
class Cylinder{ | |
Circle base; | |
double hegih; | |
pubilc: | |
Cylinder(double r,double h):base(r),heigh(h){} | |
~Cylinder(void); | |
double volume(){return base.area() * heigh;} | |
}; |
# 3.3 运算符重载
定义: 通过定义运算符的函数来实现重载
type operator sign (parameters) {}
class CVetor{ | |
Public: | |
int x,y; | |
CVector(){}; | |
CVector(int a, int b):x(a),y(b){}; | |
Cvector operator + (const CVector&); | |
}; | |
Cvector Cvector::operator+ (const CVector & param) | |
{ | |
Cvector temp; | |
temp.x = x + param.x; | |
temp.y = y + param.y; | |
return temp; | |
} |
非成员函数实现
Cvector operator+ (const CVector & param,const CVector & param_2) | |
{ | |
Cvector temp; | |
temp.x = param_2.x + param.x; | |
temp.y = param_2.y + param.y; | |
return temp; | |
} |
# 3.4 this 和成员修饰
- this 指针指向正在执行的类实体
- 成员函数通过 this 指针,访问到实体成员
用途:
- 可以检查函数参数是否时对象自己
- 在运行符 opreator = 重载的成员函数中将对象自己作为返回值输出
class Rect{ | |
int width, high; | |
pubilc: | |
Rect(int,int); | |
~Rect(void); | |
int area(); | |
} | |
Rect::area() | |
{ | |
return (this.width * this.high); | |
} |
检查是否是自己
bool Rect::isitme(Rect & param) | |
{ | |
if(¶m == this) return true; | |
else return false; | |
} |
将自己作为返回值输出
Cvector& Cvector::operator= (const CVector & param) | |
{ | |
x = param.x; | |
y = param.y; | |
return *this; | |
} |
成员函数修饰
Static
- 静态成员属于整个类,而不是某个具体的实例对象
- 静态成员变量只存储一份,供全体对象使用
<img src="C:%5CUsers%5Cjunwi%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20200726001952805.png" alt="image-20200726001952805" style="zoom: 25%;" />
Const 修饰类的话
- 就好像所有成员变量都是 const 量
- 构造函数仍能初始化成员变量
- const 成员函数泵修改成员变量
int get() const{return x;} // 不能修改成员变量
const int& get(){return x;} // 返回一个 const & 引用
const int& get()const {return x;} // 不能修改成员变量,返回一个 const & 引用
# 3.5 C++ Pointer
# Nullptr
主要是用来解决之前二义性的问题,如 code
void F(int a) | |
{ | |
cout<< a << endl; | |
} | |
void F(int *p) | |
{ | |
assert(p != NULL); | |
cout << p << endl; | |
} | |
F(0); // 调用第一个 | |
F(nullptr); // 第二个 |
# 智能指针
unique_ptr:
- 内存资源的所有权不需要共享(它没有拷贝构造函数)
- 它可以转让给另一个 unique_ptr(存在 move 构造函数)。
shared_ptr:
- 内存资源需要共享。
- 一个 shared_ptr 只有在已经没有任何其它 shared_ptr 指向其原本所指向对象时,才会销毁该对象。
weak_ptr:
- 持有被 shared_ptr 所管理对象的引用,但是不会改变引用计数值。
- weak_ptr 并不拥有它所指向的对象,因此不影响该对象的销毁与否。
- 必须调用 lock () 来获得被引用对象的 shared_ptr,通过它才能访问这个对象。
void foo(int* p) | |
{ | |
std::cout << *p << std::endl; | |
} | |
std::unique_ptr<int> p1(new int(42)); | |
std::unique_ptr<int> p2 = std::move(p1); // transfer ownership | |
if(p1) | |
foo(p1.get()); //get () 函数将返回 nullptr | |
(*p2)++; | |
if(p2) | |
foo(p2.get()); | |
// 不知直接赋值,而是需要用 move (); |
{ | |
shared_ptr<int> a; // a is empty | |
{ | |
shared_ptr<int> b( new int( 10 ) ); // allocate resource | |
a = b; // reference counter: 2 | |
{ | |
shared_ptr<int> c = a; // reference counter: 3 | |
*c = 100; | |
} // c dead, reference counter: 2 | |
} // b dead, reference counter: 1 | |
cout << *a << endl; // *a = 100 | |
} // release resource | |
std::shared_ptr<int> p1(new int(42)); | |
auto p2 = std::make_shared<int>(42); | |
//p1 和 p2 作用等价。make_shared<T > 是一个非成员函数,使用它的好处是可以一次性分配共享对象和智能指针自身的内存。而显示地使用 shared_ptr 构造函数来构造则至少需要两次内存分配 | |
/* p1 | |
1. 执行申请 数据体 (StructA) 的内存申请 | |
new StructA | |
2. 执行控制块的内存申请 | |
shared_ptr A | |
*/ | |
/* p2 | |
数据体 和 控制块的 内存一块申请 new StructA & shared_ptr A | |
*/ |
weak_ptr<int> w1; | |
{ | |
shared_ptr<int> a( new int(10) ); | |
w1 = a; //w1 基本上也不能用來做資料的存取,主要只能用來監控 a 目前的狀況 | |
} //a 的资源会被释放 | |
auto p = std::make_shared<int>(42); | |
std::weak_ptr<int> wp = p; | |
{ | |
auto sp = wp.lock(); // 必须调用 lock () 来获得被引用对象的 shared_ptr,通过它才能访问这个对象 | |
std::cout << *sp << std::endl; | |
} | |
p.reset(); | |
if(wp.expired()) | |
std::cout << "expired" << std::endl; |
# 环形引用
对象 A 持有对象 B 的强引用,对象 B 持有对象 A 的强应用,最终导致 A 和 B 都无法释放
typedef struct _StructA StructA; | |
typedef struct _StructB StructB; | |
struct _StructA | |
{ | |
std::shared_ptr<StructB> pStructB; //A 有 B 的强引用 | |
//…. | |
} ; | |
struct _StructB | |
{ | |
std::shared_ptr<StructA> pStructA; //B 有 A 的强引用 | |
//…. | |
}; | |
{ | |
std::shared_ptr<StructA> pA = std::make_shared<StructA>(); | |
std::shared_ptr<StructB> pB (new StructB()); | |
pA->pStructB = pB; | |
pB->pStructA = pA; | |
} // 超出作用域之后,A, B 资源都无法释放 | |
// 解决方案 | |
// 解决方法,其中一方使用弱引用 | |
struct _StructA | |
{ | |
std::shared_ptr<StructB> pStructB; //A 有 B 的强引用 | |
//…. | |
} ; | |
struct _StructB | |
{ | |
std::weak_ptr<StructA> pStructA; //B 有 A 的弱引用 | |
// …. | |
}; |
# 3.6 多线程
# 多任务多内存模型
枚举值 | 规则 | Type |
---|---|---|
memory_order_relaxed | 不对执行顺序做任何保证 | 存储 (store)/ 读取 (load) |
memory_order_consume | 本线程所有后续有关本操作的必须在本操作完成后执行 | 读取 (load) |
memory_order_acquire | 本线程所有后续的读操作必须在本条操作完成才能执行 | 读取 (load) |
memory_order_release | 本线程所有之前的写操作完成后才执行本操作 | 存储 (store) |
memory_order_acq_rel | 同时包含 acquire 和 release | 存储 (store)/ 读取 (load) |
memory_order_seq_cst | 全部顺序执行 | 存储 (store)/ 读取 (load) |
atomic<int> a; | |
atomic<int> b; | |
int thread_1(){ | |
int t = 1; | |
a = t; | |
b = 2; | |
} | |
int thread_2{ | |
while(b != 2); | |
cout << a << endl; //a 值多线程操作不一定为 1 | |
} | |
/* | |
1. Loadi reg3,1 | |
2. Move reg4,reg3 | |
3. Store reg4,a | |
4. Loadi reg5,2 | |
5. Store reg5,b | |
CPU 执行顺序可以为 | |
1->2->3->4->5 | |
也可能为 | |
1->4->2->5->3 | |
(1,2,3) 与 (4,5) 使用了不同指令和 Reg | |
*/ | |
atomic<int> a; | |
atomic<int> b; | |
int thread_1(){ | |
int t = 1; | |
a.store(t,memory_order_relaxed); // 没有要求 | |
b.store(2,memory_order_release); // 写操作完成后才执行本操作 | |
} | |
int thread_2(){ | |
while(b.load(memory_order_acquire) != 2); | |
cout << a.load(memory_order_relaxed); | |
//a 值永远为 1 | |
} |
# 原子操作
多个线程访问同一个全局资源的时候,能够确保所有其他的线程都不在同一时间内访问相同的资源
–std::atomic_flag
操作
- test_and_set, 如果 atomic_flag 对象被设置,则返回 true; 如果 atomic_flag 对象未被设置,则设置之,返回 false
- clear. 清除 atomic_flag 对象
使用 atomic_flag 可实现 mutex.
–std::atomic
- 对 int, char, bool 等数据结构进行原子性封装
- 利用 std::atomic 可实现数据结构的无锁设计
#include <iostream> | |
#include <atomic> | |
#include <vector> | |
#include <thread> | |
#include <sstream> | |
std::atomic_flag lock = ATOMIC_FLAG_INIT; | |
std::stringstream stream; | |
void append_numer(int x) | |
{ | |
while (lock.test_and_set()); | |
stream << "thread#" << x << "\n"; | |
lock.clear(); | |
} | |
int main() | |
{ | |
std::vector<std::thread> ths; | |
for (int i=0; i<10; i++) | |
ths.push_back(std::thread(append_numer, i)); | |
for (int i=0; i<10; i++) | |
ths[i].join(); | |
std::cout << stream.str(); | |
return 0; | |
} |
#include <iostream> | |
#include <atomic> | |
#include <vector> | |
#include <thread> | |
std::atomic<bool> ready(false); | |
std::atomic_flag winner = ATOMIC_FLAG_INIT; | |
void count (int i) | |
{ | |
while (!ready); | |
if (!winner.test_and_set()) | |
std::cout << "winner: " << i << std::endl; | |
} | |
int main() | |
{ | |
std::vector<std::thread> ths; | |
for (int i=0; i<10; i++) | |
ths.push_back(std::thread(count, i)); | |
ready = true; | |
for (int i=0; i<10; i++) | |
ths[i].join(); | |
return 0; | |
} |
# Thread_local
thread_local 关键字修饰的变量具有线程周期 (thread duration)
变量或对象在线程开始的时候被生成 (allocated),在线程结束的时候被销毁 (deallocated)
thread_local int x; //A thread-local variable at namespace scope | |
class X | |
{ | |
static thread_local std::string s; //A thread-local static class data member | |
}; | |
static thread_local std::string X::s; //The definition of X::s is required | |
void foo() | |
{ | |
thread_local std::vector<int> v; //A thread-local local variable | |
} |
std_thread
std::thread::join()
等待子线程执行完之后,主线程才可以继续执行下去,此时主线程会释放掉执行完后的子线程资源std::thread::detach()
将子线程从主线程里分离,子线程执行完成后会自己释放掉资源。分离后的线程,主线程将对它没有控制权了
bool HelloWorld::init() | |
{ | |
// 创建一个分支线程,回调到 myThread 函数里 | |
std::thread t1(&HelloWorld::myThread,this); | |
t1.join(); | |
CCLOG(“in major thread”); // 在主线程 | |
return true; | |
} | |
void HelloWorld::myThread() | |
{ | |
CCLOG("in my thread"); | |
} | |
/* | |
运行结果: | |
in my thread | |
in major thread | |
*/ | |
// --- // | |
bool HelloWorld::init() | |
{ | |
// 创建一个分支线程,回调到 myThread 函数里 | |
std::thread t1(&HelloWorld::myThread,this); | |
t1.detach(); | |
CCLOG(“in major thread”); // 在主线程 | |
return true; | |
} | |
void HelloWorld::myThread() | |
{ | |
CCLOG("in my thread"); | |
} | |
/* | |
in major thread | |
in my thread | |
或 | |
in my thread | |
in major thread | |
*/ |