未完每日更新
https://blog.csdn.net/yawdd/article/details/80010148
C++中的內(nèi)存管理

寄存器:
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