# SW Development Skill

# 1. Makefile

# 1.1 Makefile 简介

用途:

  • 描述整个工程的编译,链接的规则
  • 软件项目的自动化编译

工作过程:

  • 解析 Makefile 命令,建立依赖关系图
  • 命令运行阶段

# 1.2 Makefile 中的概念

命令基本构成:

  • 目标
  • 目标依赖
  • 命令
#目标:目标依赖
#[tab] 命令
main: a.o
	gcc -o main a.o

Tip:

  1. 命令开头必须摇头 Tab
  2. 一个规则可以没有目标依赖,仅仅描述某种操作
  3. 一个规则可以没有命令,仅仅描述依赖关系
  4. 一个规则必须有一个目标

目标:

  • 默认目标

    • 一个 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 执行过程

  1. 进入编译目录
  2. 执行 make
  3. 依赖关系解析
    • 解析 Makefile 建立依赖关系图
    • 控制解析过程:引入 Makefile,变量展开,条件执行
    • 生成依赖关系树
  4. 命令执行阶段
    • 将解析生成的依赖关系树加载到内存
    • 依照依赖关系,按顺序生成这些文件
    • 再次编译 Make 会检查文件的时间戳,判断是否过期
      • 若无过期,退出
      • 若有更新,则依赖该文件的所有依赖关系上的目标重新更新,编译生成

状态码:

0: 成功执行

1: 运行错误,返回 1

# 1.6 隐含规则

规则:

  1. 默认 .c 编译生成对应的 .o 目标文件

  2. 取消隐含规则: 使用 -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:

  1. 关于 (type*)0 是因为没有一个实例,所以使用一个 NULL 指针,这个指针为了能够引用实例化后的成员,而 NULL 是 0x000000

    然后就指导 member 就能得到绝对的偏移值。

  2. 直接使用 (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 指针,访问到实体成员

用途:

  1. 可以检查函数参数是否时对象自己
  2. 在运行符 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(&param == 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,通过它才能访问这个对象。
unique_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 ();
share_ptr
{ 
    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
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
*/
更新于

请我喝[茶]~( ̄▽ ̄)~*

Junwide Xiao 微信支付

微信支付

Junwide Xiao 支付宝

支付宝