未完每日更新
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)5鍪浴W昂凇副瀑!
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ù)庫(kù)提供的拾稳,它的機(jī)制很復(fù)雜吮炕,庫(kù)函數(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)庫(kù)函數(shù)strcpy進(jìn)行復(fù)制趣些。
比較b和a的內(nèi)容是否相同仿荆,不能用if(b==a) 來判斷,應(yīng)該用標(biāo)準(zhǔn)庫(kù)函數(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ù)庫(kù)型 弯蚜。
可以用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)嗍荆a(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++ 頭文件引用的庫(kù)在cpp文件里可以引用嗎
new創(chuàng)建對(duì)象直接使用堆空間田柔,而局部不用new定義類對(duì)象則使用椑停空間
important C++的編譯問題
tcpdump WireShark