C++PLUS

未完每日更新

https://blog.csdn.net/yawdd/article/details/80010148

C++中的內(nèi)存管理

計(jì)算機(jī)存儲(chǔ)體系
寄存器:

CPU內(nèi)部主要由控制器、運(yùn)算器和寄存器組成。
控制器負(fù)責(zé)指令的讀取和調(diào)度,運(yùn)算器負(fù)責(zé)指令的運(yùn)算執(zhí)行,寄存器負(fù)責(zé)數(shù)據(jù)的存儲(chǔ),它們之間通過CPU的內(nèi)部總線連接在一起。

寄存器擁有非常高的讀寫速度,可以看作數(shù)據(jù)在CPU內(nèi)的一個(gè)臨時(shí)存儲(chǔ)的單元。

寄存器的制作難度大,選材精,而且是集成到芯片內(nèi)部,所價(jià)格高。而內(nèi)存的成本則相對(duì)低廉,而且從工藝上來說,我們不可能在CPU內(nèi)部集成大量的存儲(chǔ)單元。


高度緩存Cache

當(dāng)程序在運(yùn)行時(shí),就可以預(yù)先將部分在內(nèi)存中要執(zhí)行的指令代碼以及數(shù)據(jù)復(fù)制到高速緩存中去,而CPU則不再每次都從內(nèi)存中讀取指令而是直接從高速緩存依次讀取指令來執(zhí)行,從而加快了整體的速度。

高速緩存又分為一級(jí)Cache和二級(jí)Cache,一級(jí)緩存集成在CPU內(nèi)部,二級(jí)緩存以前焊在主板上,現(xiàn)在也都集成在CPU內(nèi)部。

Cache成本比寄存器低,但是比內(nèi)存的制造成本高,容量要比寄存器大,但是比內(nèi)存的容量小很多。


內(nèi)存

分為只讀存儲(chǔ)器(ROM)、隨機(jī)存儲(chǔ)器(RAM)和高速緩存存儲(chǔ)器(cache)。
內(nèi)存具有“掉電信息全部消失”的特性,而外存則具有“掉電信息也不會(huì)丟失”的特性。


空間換時(shí)間

在軟件設(shè)計(jì)上有一個(gè)所謂的空間換時(shí)間的概念,就是當(dāng)兩個(gè)對(duì)象之間進(jìn)行交互時(shí)因?yàn)槎咛幚硭俣炔⒉灰恢聲r(shí),我們就需要引入緩存來解決讀寫不一致的問題。
比如文件讀寫或者socket通信時(shí),因?yàn)镮O設(shè)備的處理速度很慢,所以在進(jìn)行文件讀寫以及socket通信時(shí)總是要將讀出或者寫入的部分?jǐn)?shù)據(jù)先保存到一個(gè)緩存中,然后再統(tǒng)一的執(zhí)行讀出和寫入操作。


阻塞 與 非阻塞IO

CPU層次:
現(xiàn)代操作系統(tǒng)通常使用異步非阻塞方式進(jìn)行IO,即發(fā)出IO請(qǐng)求之后,并不等待IO操作完成,而是繼續(xù)執(zhí)行下面的指令(非阻塞),IO操作和CPU指令互不干擾(異步),最后通過中斷的方式來通知IO操作完成結(jié)果。

線程層次:
操作系統(tǒng)為了減輕程序員的思考負(fù)擔(dān),將底層的異步非阻塞的IO方式進(jìn)行封裝,把相關(guān)系統(tǒng)調(diào)用(如read,write等)以同步的方式展現(xiàn)出來。

而以同步展現(xiàn)的IO又會(huì)帶來新的問題,即為:
同步阻塞的IO會(huì)使線程掛起(后面的指令都等著IO),
同步非阻塞的IO會(huì)消耗CPU資源在輪詢

為解決這一問題,有新的解決方案
多線程(同步阻塞);
IO多路復(fù)用(select,poll,epoll)(同步非阻塞,嚴(yán)格地來講,是把阻塞點(diǎn)改變了位置);


一、內(nèi)存管理

堆棧是內(nèi)存中的一個(gè)數(shù)據(jù)結(jié)構(gòu)?。?!

1.1、內(nèi)存布局

  • 棧:
    局部變量,函數(shù)參數(shù)等存儲(chǔ)在該區(qū),由編譯器自動(dòng)分配和釋放。函數(shù)執(zhí)行結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放。
    棧的內(nèi)存空間是連續(xù)的,效率很高,但棧的內(nèi)存空間有限。

  • 堆:
    需要程序員手動(dòng)分配和釋放,一個(gè)new就要對(duì)應(yīng)一個(gè)delete。
    如果程序員沒有釋放掉,那么在程序結(jié)束后,操作系統(tǒng)會(huì)自動(dòng)回收。
    內(nèi)存空間幾乎沒有限制,內(nèi)存空間不連續(xù),因此會(huì)產(chǎn)生內(nèi)存碎片

  • 自由存儲(chǔ)區(qū):
    由malloc等分配的內(nèi)存塊,和堆十分相似的,不過它是用free來結(jié)束自己的生命的。

  • 全局/靜態(tài)存儲(chǔ)區(qū):
    全局變量和靜態(tài)變量被分配到同一塊內(nèi)存中,它們共用一塊存儲(chǔ)區(qū)。
    全局變量,靜態(tài)變量分配到該區(qū),到程序結(jié)束時(shí)自動(dòng)釋放。
    包括DATA段(全局初始化區(qū))與BBS段(全局未初始化段):在程序執(zhí)行前BBS段自動(dòng)清零,所以未初始化的全局變量和靜態(tài)變量在程序執(zhí)行前已經(jīng)成為0。

  • 常量存儲(chǔ)區(qū)
    存放的是常量,不允許修改。

舉例

 int a = 0; //全局初始化區(qū)
 char *p1; //全局未初始化區(qū)
    void main()
   {
        int b; //棧
        char s[] = "abc"; //棧
        char *p2; //棧
        char *p3 = "123456"; //123456{post.content}在常量區(qū),p3在棧上
  }

1.2、堆和棧的區(qū)別

管理方式不同:手動(dòng)——自動(dòng)
空間大小不同:不連續(xù),但幾乎沒有限制——連續(xù),但是有限
產(chǎn)生碎片不同:

生長(zhǎng)方向不同:對(duì)于堆來說,是向著內(nèi)存地址增加的方向;對(duì)于棧來講,是向著內(nèi)存地址減小的方向增長(zhǎng)。

分配方式不同:堆都是動(dòng)態(tài)分配的,沒有靜態(tài)分配的堆。
棧的靜態(tài)分配是編譯器完成的,比如局部變量的分配。
棧的動(dòng)態(tài)分配由alloca函數(shù)進(jìn)行分配,但是棧的動(dòng)態(tài)分配和堆是不同的,他的動(dòng)態(tài)分配是由編譯器進(jìn)行釋放,無需我們手工實(shí)現(xiàn)

分配效率不同:
棧是機(jī)器系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu),壓棧出棧都有專門的指令執(zhí)行,這就決定了棧的效率比較高。
堆則是C/C++函數(shù)庫提供的,它的機(jī)制很復(fù)雜,庫函數(shù)會(huì)按照一定的算法在堆內(nèi)存中搜索可用的足夠大小的空間。

1.3、內(nèi)存分配的問題

  • a.內(nèi)存分配未成功,卻使用了它 :在使用內(nèi)存之前檢查指針是否為NULL。
    如果指針p是函數(shù)的參數(shù),那么在函數(shù)的入口處用assert()
    如果是用malloc或new來申請(qǐng)內(nèi)存,應(yīng)該用if(p==NULL) 進(jìn)行防錯(cuò)處理

補(bǔ)充:assert()作用是如果它的條件返回錯(cuò)誤,則終止程序執(zhí)行,一般不使用。

  • b. 內(nèi)存分配雖然成功,但是尚未初始化就引用它:創(chuàng)建數(shù)組,別忘了賦初值

  • c. 操作越過了內(nèi)存的邊界:注意下標(biāo)

  • d. 忘記了釋放內(nèi)存:malloc與free的使用次數(shù)一定要相同(new/delete同理),否則肯定有錯(cuò)誤。

  • e. 釋放了內(nèi)存卻繼續(xù)使用它:用free或delete釋放了內(nèi)存之后,立即將指針設(shè)置為NULL,防止產(chǎn)生“野指針”。

1.3、指針與數(shù)組

  • 數(shù)組名對(duì)應(yīng)著(而不是指向)一塊內(nèi)存,其地址與容量在生命期內(nèi)保持不變,只有數(shù)組的內(nèi)容可以改變。

  • 指針可以隨時(shí)指向任意類型的內(nèi)存塊,它的特征是“可變”。

若想把數(shù)組a的內(nèi)容復(fù)制給數(shù)組b,不能用語句 b = a ,否則將產(chǎn)生編譯錯(cuò)誤。應(yīng)該用標(biāo)準(zhǔn)庫函數(shù)strcpy進(jìn)行復(fù)制。
比較b和a的內(nèi)容是否相同,不能用if(b==a) 來判斷,應(yīng)該用標(biāo)準(zhǔn)庫函數(shù)strcmp進(jìn)行比較。


2 多線程

2.1 Linux環(huán)境下的C++多線程編程

什么是臨界區(qū)
臨界區(qū):當(dāng)兩個(gè)線程競(jìng)爭(zhēng)同一資源時(shí),如果對(duì)資源的訪問順序敏感,就稱存在競(jìng)態(tài)條件。導(dǎo)致競(jìng)態(tài)條件發(fā)生的代碼區(qū)稱作臨界區(qū)。

2.1.1 noncopyable

同意程序?qū)崿F(xiàn)一個(gè)不可復(fù)制類。
定義一個(gè)類時(shí) C++會(huì)默認(rèn)生成 復(fù)制構(gòu)造函數(shù)復(fù)制賦值操作符。

class empty_class{
public:
    empty_class(const empty_class &){...}
    empty_class & operator=(const empty_class &){...}
};

原因:有些對(duì)象是獨(dú)一無二的,作備份不合邏輯。

2.1.2 pthread_mutex_t

linux線程互斥量pthread_mutex_t,當(dāng)另一個(gè)線程也要訪問這個(gè)變量時(shí),發(fā)現(xiàn)這個(gè)變量被鎖住了,無法訪問,它就會(huì)一直等待。

2.1.2 pid_t

pid_t是一個(gè)typedef定義類型,實(shí)際上就是一個(gè)int類型。用它來表示進(jìn)程id類型

2.1.3 nullptr與NULL

在C++中,一個(gè)空指針要么是一個(gè)字面值整形,要么是一個(gè)std::nullptr_t。
C++中的NULL,其實(shí)就是一個(gè)0,這會(huì)導(dǎo)致很多問題,func(NULL)會(huì)去調(diào)用void func(int),所以引入nullptr。

2.1.4 boost::bind

TODO:

2.1.5 pthread_cond_t

pthread_cond_t表示多線程的條件變量,用于控制線程等待和就緒的條件。
pthread_cond_destroy用于銷毀一個(gè)條件變量

2.1.6 __thread

__thread 關(guān)鍵字表示每一個(gè)線程都有一份獨(dú)立的實(shí)體,且互不干擾。
只能修飾:基本變量、指針變量、不帶自定義構(gòu)造函數(shù)和析構(gòu)函數(shù)的類。

2.1.7 extern

a.C++ primer 4 -- 669 指定使用其他語言編寫的函數(shù)

b.如果想聲明一個(gè)變量而非定義它,就在變量名前添加extern關(guān)鍵字

2.1.8 timespec

struct timespec 和 struct timeval 是Linux環(huán)境下的兩個(gè)時(shí)間struct,主要用于由函數(shù)int clock_gettime(clockid_t, struct timespec *)獲取特定時(shí)鐘的時(shí)間,其中的clockid_t用以下幾種時(shí)鐘:

  • CLOCK_REALTIME 統(tǒng)當(dāng)前時(shí)間,從1970年1.1日算起
  • CLOCK_MONOTONIC 系統(tǒng)的啟動(dòng)時(shí)間,不能被設(shè)置
  • CLOCK_PROCESS_CPUTIME_ID 本進(jìn)程運(yùn)行時(shí)間
  • CLOCK_THREAD_CPUTIME_ID 本線程運(yùn)行時(shí)間

timespec有兩個(gè)成員,一個(gè)是秒,一個(gè)是納秒, 所以最高精確度是納秒。

2.1.9 跨平臺(tái)的數(shù)據(jù)格式

數(shù)據(jù)類型特別是int相關(guān)的類型在不同位數(shù)機(jī)器的平臺(tái)下長(zhǎng)度不同,為了保證平臺(tái)的通用性,程序中盡量不要使用long數(shù)據(jù)庫型 。
可以用int64_t來表示C++中的long long

2.1.10 static_cast

static_cast是一個(gè)強(qiáng)制類型轉(zhuǎn)換操作符。強(qiáng)制類型轉(zhuǎn)換,也稱為顯式轉(zhuǎn)換。

double a = 1.999;
int b = static_cast<double>(a); 

使用static_cast可以找回存放在void*指針中的值。

    double a = 1.999;
    void * vptr = & a;
    double * dptr = static_cast<double*>(vptr);

static_cast也可以用在于基類與派生類指針或引用類型之間的轉(zhuǎn)換。然而它不做運(yùn)行時(shí)的檢查,不如dynamic_cast安全。

2.1.11 c++ 時(shí)間類型

Unix時(shí)間戳是一種時(shí)間表示方式,定義為從格林威治時(shí)間1970年01月01日00時(shí)00分00秒起至現(xiàn)在的總秒數(shù)。
time_t 這種類型(struct)就是用來存儲(chǔ)從1970年到現(xiàn)在經(jīng)過了多少秒。

2.1.13 靜態(tài)斷言

static_assert(常量表達(dá)式,提示字符串)
如果第一個(gè)參數(shù)常量表達(dá)式的值為真(true或者非零值),那么static_assert不做任何事情,就像它不存在一樣,否則會(huì)產(chǎn)生一條編譯錯(cuò)誤,錯(cuò)誤位置就是該static_assert語句所在行,錯(cuò)誤提示就是第二個(gè)參數(shù)提示字符串。

2.1.14 std::is_same

std::is_same 判斷類型是否一致,兩個(gè)一樣的類型會(huì)返回true。

2.1.15 棧追蹤

獲取當(dāng)前線程的調(diào)用堆棧,獲取的信息將會(huì)被存放在buffer中
int backtrace(void **buffer,int size),參數(shù) size 用來指定buffer中可以保存多少個(gè) void*元素

char ** backtrace_symbols (void *const *buffer, int size)將從backtrace函數(shù)獲取的信息轉(zhuǎn)化為一個(gè)字符串?dāng)?shù)組,size是該數(shù)組中的元素個(gè)數(shù)

2.1.16
2.1.50 內(nèi)聯(lián)函數(shù) inline

大多數(shù)機(jī)器上,調(diào)用函數(shù)要做很多工作,調(diào)用前要先保存寄存器,并且在返回時(shí)恢復(fù)。程序還必須轉(zhuǎn)向一個(gè)新的位置。
內(nèi)聯(lián)函數(shù)保證他在每一個(gè)調(diào)用節(jié)點(diǎn)上,內(nèi)聯(lián)地展開,適用于小但是調(diào)用很頻繁的函數(shù)

2.2 強(qiáng)引用與弱引用

2.2.1 weak_ptr

weak_ptr 是一種不控制對(duì)象生命周期的智能指針, 它指向一個(gè) shared_ptr 管理的對(duì)象.
不會(huì)改變shared_ptr的引用計(jì)數(shù)。不論是否有weak_ptr指向,一旦最后一個(gè)指向?qū)ο蟮膕hared_ptr被銷毀,對(duì)象就會(huì)被釋放。

a.升級(jí)
如果對(duì)象存在,weak_ptr 的 lock()函數(shù)返回一個(gè)指向共享對(duì)象的shared_ptr,此時(shí)如果原來的shared_ptr被銷毀,則該對(duì)象的生命期將被延長(zhǎng)至這個(gè)臨時(shí)的shared_ptr同樣被銷毀為止。
如果weak_ptr指向的對(duì)象被銷毀,否則返回一個(gè)空shared_ptr。

b.打破循環(huán)引用

class ClassA
{
public:
    ClassA() {}
    ~ClassA() {}

private:
    weak_ptr<ClassB> pb;  // 在A中引用B
};

class ClassB
{
public:
    ClassB() {}
    ~ClassB() {}

private:
    weak_ptr<ClassA> pa;  // 在B中引用A
};

int main() {
    shared_ptr<ClassA> spa = make_shared<ClassA>();
    shared_ptr<ClassB> spb = make_shared<ClassB>();
    // 因?yàn)闆]改變shared_ptr的引用計(jì)數(shù),此時(shí)引用計(jì)數(shù)為1,超過作用域后自動(dòng)釋放
}

因?yàn)闆]改變shared_ptr的引用計(jì)數(shù),此時(shí)引用計(jì)數(shù)為1,超過作用域后自動(dòng)釋放。

2.2.2 enable_shared_from this
int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);

此時(shí)這兩個(gè)shared_ptr指針,所以當(dāng)其中一個(gè)在析構(gòu)時(shí)有可能資源已經(jīng)被釋放了。
Similarly, if a member function needs a shared_ptr object that owns the object that it's being called on, it can't just create an object on the fly:

struct S : enable_shared_from_this<S>
{
  shared_ptr<S> not_dangerous()
  {
    return shared_from_this();
  }
};

When you do this, keep in mind that the object on which you call shared_from_this must be owned by a shared_ptr object.

2.2.3 臨界區(qū)

臨界區(qū)指的是一個(gè)訪問共用資源的程序片段

2.2.4 copy on write——COW

有多個(gè)調(diào)用者使用相同的資源時(shí),他們會(huì)共同獲取相同的指針指向相同的資源。內(nèi)核此時(shí)并不復(fù)制。
直到某個(gè)調(diào)用者試圖修改資源的內(nèi)容時(shí),內(nèi)核才會(huì)真正復(fù)制一份專用副本(private copy)給該調(diào)用者,而其他調(diào)用者所見到的最初的資源仍然保持不變。
這是一種種延時(shí)懶惰策略。

2.2.5 unique_ptr

unique_ptr 獨(dú)占所指向的對(duì)象, 同一時(shí)刻只能有一個(gè) unique_ptr 指向給定對(duì)象,在 unique_ptr 離開作用域時(shí)釋放該對(duì)象。

在下列兩者之一發(fā)生時(shí)用關(guān)聯(lián)的刪除器釋放對(duì)象:

  • 銷毀了管理的 unique_ptr 對(duì)象
  • 通過 operator=reset() 賦值另一指針給管理的 unique_ptr 對(duì)象。

shared_ptr的unique函數(shù):檢查所管理對(duì)象是否僅由當(dāng)前 shared_ptr 的實(shí)例管理

2.2.5 std::const_iterator

const_iterator 可遍歷,不可改變所指元素

const iterator 不可遍歷,可改變所指元素:iterator本身里面存的是指針,指針不能改變,也就是不能指向其他的位置

2.2.6 shared_ptr 的線程安全性

同一個(gè)shared_ptr對(duì)象可以被多線程同時(shí)讀取。
不同的shared_ptr對(duì)象可以被多線程同時(shí)修改(即使這些shared_ptr對(duì)象管理著同一個(gè)對(duì)象的指針)

2.2.7 size_t

與機(jī)器相關(guān)的unsigned類型,其大小足以保證存儲(chǔ)內(nèi)存中對(duì)象的大小。
size_t在32位架構(gòu)上是4字節(jié),在64位架構(gòu)上是8字節(jié).

2.2.8 implicit_cast
struct top {};  //最頂層的父類
struct mid_a : top {}; 
struct mid_b : top {};
struct bottom : mid_a, mid_b {};  //最底層的派生類

之前的static_cast太強(qiáng)大了, 強(qiáng)大到可以進(jìn)行”down-cast”. 于是編譯器沒有任何的警告, 就可以把一個(gè)top類型的引用給強(qiáng)制轉(zhuǎn)換成了min_a的引用.

在C++世界的英文里, 我們說”convert”通常指”implicit convert”, 而”cast”指explicit cast(顯式)

implicit cast——隱式cast

2.3 兩個(gè)類互相引用

根本原因:定義A的時(shí)候,A的里面有B,所以就需要去查看B的占空間大小,但是查看的時(shí)候又發(fā)現(xiàn)需要知道A的占空間大小,造成死循環(huán)。

2.3.1 解決方案
#include <MutexLock.h>
#include <set>

class Request;

class Inventory {
public:
    void add(Request *request) {
       // 
    };
private:
    mutable MutexLock mutexLock;
    std::set<Request *> requests;
};

在Inventory.h中,采用的class Request的前置聲明,但是在class Inventory的聲明中只能定義Class Request的指針或引用。

原理:雖然在B的定義文件中并沒有導(dǎo)入A的頭文件,不知道A的占空間大小,但是由于在B中調(diào)用A的時(shí)候用的指針形式,B只知道指針占4個(gè)字節(jié)就可以,不需要知道A真正占空間大小。

2.3.2 前置聲明
2.3.3 字節(jié)的占用
2.3.4 C++類的this
2.3.4 boost::function()
2.3.5 mangle和demangle

ABI是Application Binary Interface的簡(jiǎn)稱。 C/C++發(fā)展的過程中,二進(jìn)制兼容一直是個(gè)問題。不同編譯器廠商編譯的二進(jìn)制代碼之間兼容性不好,甚至同一個(gè)編譯器的不同版本之間兼容性也不好。
C/C++語言在編譯以后,函數(shù)的名字會(huì)被編譯器修改,改成編譯器內(nèi)部的名字,這個(gè)名字會(huì)在鏈接的時(shí)候用到。
識(shí)別C++編譯以后的函數(shù)名的過程,就叫demangle。

應(yīng)用:打印異常日志

2.3.6 volatile

在匯編層面反映出來,就是兩條語句,下一條語句不會(huì)直接使用上一條語句對(duì)應(yīng)的volatile變量的寄存器內(nèi)容,而是重新從內(nèi)存中讀取。

2.3.10 RTTI

2.X 之補(bǔ)充 — 移動(dòng)右值引用 ( C++11特性 )

2.X.1右值引用
  • a.判斷左值與右值
    典型情況下左值和右值可以通過在賦值表達(dá)式中的位置進(jìn)行判斷,在等號(hào)左邊的為左值、等號(hào)右邊的為右值
    另外一個(gè)判別方法是:可以取地址、有名字的就是左值,否則就是右值。

  • b.左值與右值的區(qū)別
    左值和右值都是針對(duì)表達(dá)式而言的,左值是指表達(dá)式結(jié)束后依然存在的持久對(duì)象,右值是指表達(dá)式結(jié)束時(shí)就不再存在的臨時(shí)對(duì)象
    int b = 20; //這里b是左值 20是右值 ,因?yàn)檫@個(gè)表達(dá)式過后 20將不存在了 而b依然存在

  • c.右值引用的初始化
    右值引用,是對(duì)臨時(shí)對(duì)象的一種引用,它是在初始化時(shí)完成引用的,右值引用可以在初始化后改變臨時(shí)對(duì)象的值。
    int &&i = 1; i綁定到了右值1

2.X.2 移動(dòng)MOVE
int i = 1;
int&& rr = std::move(i);

移動(dòng)操作竊取了對(duì)象資源的控制權(quán),從而避免了不必要的拷貝。

2.X.3 四行代碼的故事

第一行: int i = getVar();
這行代碼會(huì)產(chǎn)生兩種類型的值,一種是左值i,一種是函數(shù)getVar()返回的臨時(shí)值,這個(gè)臨時(shí)值在表達(dá)式結(jié)束后就銷毀了,而左值i在表達(dá)式結(jié)束后仍然存在,這個(gè)臨時(shí)值就是右值

第二行: T&& k = getVar();
getVar()產(chǎn)生的臨時(shí)值不會(huì)像第一行代碼那樣,在表達(dá)式結(jié)束之后就銷毀了,而是會(huì)被“續(xù)命”,他的生命周期將會(huì)通過右值引用得以延續(xù),和變量k的聲明周期一樣長(zhǎng)。

第三行: T(T&& a) : m_val(val){ a.m_val=nullptr; }
移動(dòng)構(gòu)造函數(shù)

class A
{
public:
    A() :m_ptr(new int(0)){}
    A(const A& a):m_ptr(new int(*a.m_ptr)) //深拷貝的拷貝構(gòu)造函數(shù)
    {
        cout << "copy construct" << endl;
    }
    A(A&& a) :m_ptr(a.m_ptr)
    {
        a.m_ptr = nullptr;
        cout << "move construct" << endl;
    }
    ~A(){ delete m_ptr;}
private:
    int* m_ptr;
};
int main(){
    A a = Get(false); 
} 
輸出:
construct
move construct
move construct

輸出結(jié)果表明,并沒有調(diào)用拷貝構(gòu)造函數(shù),只調(diào)用了移動(dòng)構(gòu)造函數(shù),它的參數(shù)是一個(gè)右值引用類型。

std::list< std::string> tokens;//省略初始化...
std::list< std::string> t = tokens; //這里存在拷貝 

std::list< std::string> tokens;
std::list< std::string> t = std::move(tokens);  //這里沒有拷貝

使用move幾乎沒有任何代價(jià),只是轉(zhuǎn)換了資源的所有權(quán)。他實(shí)際上將左值變成右值引用,然后應(yīng)用移動(dòng)語義,調(diào)用移動(dòng)構(gòu)造函數(shù),就避免了拷貝,提高了程序性能。如果一個(gè)對(duì)象內(nèi)部有較大的堆內(nèi)存或者動(dòng)態(tài)數(shù)組時(shí),很有必要寫move語義的拷貝構(gòu)造函數(shù)和賦值函數(shù),避免無謂的深拷貝,以提高性能。

第四行:

void processValue(int& a){ cout << "lvalue" << endl; }
void processValue(int&& a){ cout << "rvalue" << endl; }
template <typename T>
void forwardValue(T&& val)
{
    processValue(std::forward<T>(val)); //照參數(shù)本來的類型進(jìn)行轉(zhuǎn)發(fā)。
}
void Testdelcl()
{
    int i = 0;
    forwardValue(i); //傳入左值 
    forwardValue(0);//傳入右值 
}
輸出:
lvaue 
rvalue

按照參數(shù)的實(shí)際類型進(jìn)行轉(zhuǎn)發(fā)。

2.X.4 深拷貝與淺拷貝

淺拷貝是增加了一個(gè)指針,指向原來已經(jīng)存在的內(nèi)存。
PS:要注意淺拷貝的指針被析構(gòu)兩次。

而深拷貝是增加了一個(gè)指針,并新開辟了一塊空間。

2.4 Reactor模式——一種高性能IO

2.4.1 原始的網(wǎng)絡(luò)編程思想

while(true){
socket = accept();
new thread(socket);
}
while循環(huán)不斷監(jiān)聽端口是否有新的套接字連接,配合多線程,每一個(gè)連接用一個(gè)線程處理。
缺點(diǎn):如果連接數(shù)太高,系統(tǒng)無法承受,如果使用線程池,會(huì)導(dǎo)致線程的粒度太大。每一個(gè)線程把一次交互的事情全部做了。

2.4.2 基于事件驅(qū)動(dòng)

應(yīng)該把一次連接的操作分為更細(xì)的粒度,這些更細(xì)的粒度是更小的線程。整個(gè)線程池的數(shù)目會(huì)翻倍,但是線程更簡(jiǎn)單,任務(wù)更加單一。
這其實(shí)就是Reactor出現(xiàn)的原因,在Reactor中,這些被拆分的小線程或者子過程對(duì)應(yīng)的是handler,每一種handler會(huì)出處理一種event

2.5 無鎖化編程

2.5.1 RAM與ROM

RAM:隨機(jī)存取存儲(chǔ)器random access memory,是與CPU直接交換數(shù)據(jù)的內(nèi)部存儲(chǔ)器,也叫內(nèi)存。它可以隨時(shí)讀寫,而且速度很快。當(dāng)電源關(guān)閉時(shí)RAM不能保留數(shù)據(jù)。
ROM:ROM是Read Only Memory的縮寫

2.5.2 原子操作

鎖的缺點(diǎn):lock鎖的是FSB,前端串行總線,這個(gè)FSB是處理器CPU和RAM之間的總線,鎖住FSB,就能阻止其他處理器從RAM獲取數(shù)據(jù)。當(dāng)然這種操作開銷相當(dāng)大,只能操作小的內(nèi)存可以這樣做,想想有memcpy,如果操作一大片內(nèi)存,鎖內(nèi)存,那么代價(jià)太大了。

原子操作:所謂原子操作是指不會(huì)被線程調(diào)度機(jī)制打斷的操作;這種操作一旦開始,就一直運(yùn)行到結(jié)束,中間不會(huì)有任何 context switch (切換到另一個(gè)線程)。

_sync_fetch_and_add,先fetch,然后自加,返回的是自加以前的值。
以count = 4為例,調(diào)用__sync_fetch_and_add(&count,1)之后,返回值是4,然后,count變成了5.
__sync_bool_compare_and_swap

三、IO多路復(fù)用

struct pollfd {
    int fd;         /* 文件描述符 */
    short events;   /* 等待的事件 */
    short revents;  /* 實(shí)際發(fā)生了的事件 */
} ;

后面的fd均指文件描述符

3.1、流

不管是文件,還是套接字,還是管道,我們都可以把他們看作流。

3.2、阻塞與非阻塞 (線程層次)

可以簡(jiǎn)單理解為調(diào)用一個(gè)IO操作能不能立即得到返回應(yīng)答,如果不能立即獲得返回,需要等待,那就阻塞了

3.3、同步與異步

同步IO操作將導(dǎo)致請(qǐng)求的進(jìn)程一直被blocked,直到IO操作完成。從這個(gè)層次來,阻塞IO、非阻塞IO操作、IO多路復(fù)用都是同步IO。

3.4、多路復(fù)用IO

阻塞I/O有一個(gè)比較明顯的缺點(diǎn)是在I/O阻塞模式下,一個(gè)線程只能處理一個(gè)流的I/O事件。

多路復(fù)用IO也是阻塞IO,只是阻塞的方法是select/poll/epoll。select/epoll的好處就在于單個(gè)process就可以同時(shí)處理多個(gè)網(wǎng)絡(luò)連接的IO。它的基本原理是select/epoll這個(gè)函數(shù)會(huì)不斷輪詢所負(fù)責(zé)的IO操作,當(dāng)某個(gè)IO操作有數(shù)據(jù)到達(dá)時(shí),就通知用戶進(jìn)程。然后由用戶進(jìn)程去操作IO。

3.6、Linux的用戶空間(User space)和內(nèi)核空間( Kernel space)

x86 CPU采用了段頁式地址映射模型。進(jìn)程代碼中的地址為邏輯地址,經(jīng)過段頁式地址映射后,才真正訪問物理內(nèi)存


Linux 操作系統(tǒng)和驅(qū)動(dòng)程序運(yùn)行在內(nèi)核空間,應(yīng)用程序運(yùn)行在用戶空間,兩者不能簡(jiǎn)單地使用指針傳遞數(shù)據(jù)。

3.6、select

select本質(zhì)上是通過設(shè)置或者檢查存放fd標(biāo)志位的數(shù)據(jù)結(jié)構(gòu)來進(jìn)行下一步處理。

僅僅知道有I/O事件發(fā)生了,卻并不知道是哪那幾個(gè)流(可能有一個(gè),多個(gè),甚至全部),只能無差別輪詢所有流,找出能讀出數(shù)據(jù),或者寫入數(shù)據(jù)的流,對(duì)他們進(jìn)行操作。
1024- 32 || 2048 -64

3.7、poll

poll本質(zhì)上和select沒有區(qū)別,它將用戶傳入的數(shù)組拷貝到內(nèi)核空間,然后查詢每個(gè)fd對(duì)應(yīng)的設(shè)備狀態(tài),如果設(shè)備就緒則在設(shè)備等待隊(duì)列中加入一項(xiàng)并繼續(xù)遍歷。
如果遍歷完所有fd后沒有發(fā)現(xiàn)就緒設(shè)備,則掛起當(dāng)前進(jìn)程,直到設(shè)備就緒或者主動(dòng)超時(shí),被喚醒后它又要再次遍歷fd。

它沒有最大連接數(shù)的限制,原因是它是基于鏈表來存儲(chǔ)的

3.8、epoll

Epoll最大的優(yōu)點(diǎn)就在于它只管“活躍”的連接,而跟連接總數(shù)無關(guān)。

3.9、

3.9.1、__builtin_expect

指令的寫法為:__builtin_expect(EXP, N),意思是:EXP==N的概率很大。
__builtin_expect() 是 GCC (version >= 2.96)提供給程序員使用的,目的是將“分支轉(zhuǎn)移”的信息提供給編譯器,這樣編譯器可以對(duì)代碼進(jìn)行優(yōu)化,以減少指令跳轉(zhuǎn)帶來的性能下降
例子:GCC編譯的指令會(huì)優(yōu)先讀取 y = -1

int x, y;
 if(unlikely(x > 0))
    y = 1; 
else 
    y = -1;
3.9.1、GCC

GCC(GNU Compiler Collection,GNU編譯器套件),是由 GNU 開發(fā)的編程語言編譯器。

四、網(wǎng)絡(luò)編程

4.1 UNIX環(huán)境高級(jí)編程

4.1.1 readv和writev函數(shù)
#include<sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
//若成功則返回已讀,寫的字節(jié)數(shù),若出錯(cuò)則返回-1。
4.1.2 C++中的字節(jié)序

字節(jié)序的含義:大于一個(gè)字節(jié)類型的數(shù)據(jù)在內(nèi)存中的存放順序。比如short 或者int在不同的字節(jié)序存儲(chǔ)結(jié)果是不一樣的。
大字節(jié)序(Big-Endian):高位字節(jié)排放在內(nèi)存的低地址端,低位字節(jié)排放在內(nèi)存的高地址端
小字節(jié)序(Little-Endian):低位字節(jié)排放在內(nèi)存的低地址端,高位字節(jié)排放在內(nèi)存的高地址端。

4.1.2 char 與 uint8_t

一個(gè)是字符類型,一個(gè)是超短無符號(hào)整型,他們唯一一樣的地方就是占內(nèi)存大小一樣。

序列化:將對(duì)象或數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)為字節(jié)序列的過程。

4.1.3 reinterpret_cast

Reinterpret_cast:編譯器不會(huì)做任何檢查,截?cái)?,補(bǔ)齊的操作,只是把比特位拷貝過去.
所以 reinterpret_cast 常常被用作不同類型指針間的相互轉(zhuǎn)換,因?yàn)樗蓄愋偷闹羔樀拈L(zhǎng)度都是一致的(32位系統(tǒng)上都是4字節(jié)),按比特位拷貝后不會(huì)損失數(shù)據(jù).

2.6 event loop

所有同步任務(wù)都在主線程上執(zhí)行
異步任務(wù)指的是,不進(jìn)入主線程、而進(jìn)入任務(wù)隊(duì)列的任務(wù),只有任務(wù)隊(duì)列通知主線程,某個(gè)異步任務(wù)可以執(zhí)行了,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行。
主線程從"任務(wù)隊(duì)列"中讀取事件,這個(gè)過程是循環(huán)不斷的。

為什么不能用if來等待條件變量?spurious wakeup

event loop

計(jì)算機(jī)的內(nèi)存回收算法
HTTP中的GET和POST
C++ 頭文件引用的庫在cpp文件里可以引用嗎

new創(chuàng)建對(duì)象直接使用堆空間,而局部不用new定義類對(duì)象則使用??臻g

important C++的編譯問題

tcpdump WireShark

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.C和C++的區(qū)別?C++的特性?面向?qū)ο缶幊痰暮锰帲?答:c++在c的基礎(chǔ)上增添類,C是一個(gè)結(jié)構(gòu)化語言,它的重...
    杰倫哎呦哎呦閱讀 9,995評(píng)論 0 45
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,789評(píng)論 11 349
  • 1. 基礎(chǔ)知識(shí) 1.1、 基本概念、 功能 馮諾伊曼體系結(jié)構(gòu)1、計(jì)算機(jī)處理的數(shù)據(jù)和指令一律用二進(jìn)制數(shù)表示2、順序執(zhí)...
    yunpiao閱讀 5,780評(píng)論 1 22
  • 1. C++基礎(chǔ)知識(shí)點(diǎn) 1.1 有符號(hào)類型和無符號(hào)類型 當(dāng)我們賦給無符號(hào)類型一個(gè)超出它表示范圍的值時(shí),結(jié)果是初始值...
    Mr希靈閱讀 18,158評(píng)論 3 82
  • 搬運(yùn)自??途W(wǎng)大神總結(jié) extern關(guān)鍵字 extern修飾變量是個(gè)聲明,此變量/函數(shù)是在別處定義的,要在此處引用 ...
    leon4ever閱讀 3,921評(píng)論 0 9

友情鏈接更多精彩內(nèi)容