大-杂-烩
計(jì)算機(jī)網(wǎng)絡(luò)
在利用網(wǎng)絡(luò)socket進(jìn)行通信時(shí),選擇tcp協(xié)議進(jìn)行交互,利用send函數(shù)進(jìn)行數(shù)據(jù)發(fā)送,我們都知道send函數(shù)的返回結(jié)果為0時(shí)代表對端關(guān)閉了連接,那么如果發(fā)送0字節(jié)數(shù)據(jù)時(shí)是不是也會返回0?這樣會不會有歧義呢?
答:調(diào)用send函數(shù)發(fā)送0字節(jié)數(shù)據(jù)時(shí),send(,0,) 第三個(gè)參數(shù)代表發(fā)送的字節(jié)數(shù),本端的操作系統(tǒng)的協(xié)議棧并不會把數(shù)據(jù)發(fā)送出去(這里可以通過tcpdump截包佐證只有tcp建鏈的包,后續(xù)沒有任何數(shù)據(jù)包),但是返回的結(jié)果依然是0,所以這里確實(shí)是會有一點(diǎn)歧義。對于接收端而言,并不會有任何響應(yīng),另外如果是阻塞式的socket,那么接收端的緩沖區(qū)并沒有數(shù)據(jù)收到,所以程序會阻塞在recv處(可以通過gdb運(yùn)行程序查看)。另外發(fā)送0字節(jié)的數(shù)據(jù)是沒有任何意義的,所以盡量避免。
2.陳碩關(guān)于網(wǎng)絡(luò)編程時(shí)提出的幾個(gè)問題
程序在本機(jī)測試正常,放到網(wǎng)絡(luò)運(yùn)行上就經(jīng)常出現(xiàn)數(shù)據(jù)收不全的情況?
TCP 協(xié)議真的有所謂的“粘包問題”嗎?該如何設(shè)計(jì)打包拆包的協(xié)議?又該如何編碼實(shí)現(xiàn)才不會掉到陷阱里?
帶外數(shù)據(jù)(OOB)、信號驅(qū)動IO這些高級特性到底有沒有用?
網(wǎng)絡(luò)協(xié)議格式該怎么設(shè)計(jì)?發(fā)送 C struct 會有對齊方面的問題嗎?對方不用 C/C++ 怎么通信? 將來服務(wù)端軟件升級,需要在協(xié)議中增加一個(gè)字段,現(xiàn)有的客戶端就必須強(qiáng)制升級?
要處理幾千上萬的并發(fā)連接,似乎書上講的傳統(tǒng) fork() 模型應(yīng)付不過來,該用哪種并發(fā)模型呢? 試試 select、poll、epoll 這種 IO 復(fù)用模型吧,又感覺非阻塞IO陷阱重重,怎么程序的 CPU 使用率一直是100%?
要不改用現(xiàn)成的 libevent 網(wǎng)絡(luò)庫吧,怎么查詢一下數(shù)據(jù)庫就把其他連接上的請求給耽誤了?
再用個(gè)線程池吧。萬一發(fā)回響應(yīng)的時(shí)候?qū)Ψ揭呀?jīng)斷開連接了怎么辦?會不會串話?
讀過《UNIX 環(huán)境高級編程》,想用多線程來發(fā)揮多核 CPU 的效率, 但對程序該用哪種多線程模型感到一頭霧水? 有沒有值得推薦的適用面廣的多線程 IO 模型? 互斥器、條件變量、讀寫鎖、信號量這些底層同步原語哪些該用哪些不該用? 有沒有更高級的同步設(shè)施能簡化開發(fā)? 《UNIX 網(wǎng)絡(luò)編程(第二卷)》介紹的那些琳瑯滿目的IPC機(jī)制到底用哪個(gè)才能兼顧開發(fā)效率與可伸縮性?
網(wǎng)絡(luò)編程和多線程編程的基礎(chǔ)打得差不多,開始實(shí)際做項(xiàng)目了,更多問題撲面而來:
網(wǎng)上聽人說服務(wù)端開發(fā)要做到 7x24 運(yùn)行,為了防止內(nèi)存碎片連動態(tài)內(nèi)存分配都不能用, 那豈不是連 C++ STL 也一并禁用了?硬件的可靠性高到值得去這么做嗎?
傳聞服務(wù)端開發(fā)主要通過日志來查錯(cuò),那么日志里該寫些什么?日志是寫給誰看的?怎樣寫日志才不會影響性能?
分布式系統(tǒng)跟單機(jī)多進(jìn)程到底有什么本質(zhì)區(qū)別?心跳協(xié)議為什么是必須的,該如何實(shí)現(xiàn)?
C++ 的大型工程該如何管理?庫的接口如何設(shè)計(jì)才能保證升級的時(shí)候不破壞二進(jìn)制兼容性?
這本《Linux 多線程服務(wù)端編程》中,作者憑借多年的工程實(shí)踐經(jīng)驗(yàn)試圖解答以上疑問。當(dāng)然,內(nèi)容還遠(yuǎn)不止這些……
—摘自陳碩博客
操作系統(tǒng)
數(shù)據(jù)結(jié)構(gòu)
GDB調(diào)試
1.編譯時(shí)增加編譯選項(xiàng) -fstack -protector 可以在檢測到緩沖區(qū)溢出時(shí),立刻終止正在執(zhí)行的程序,并提示其檢測到緩沖區(qū)存在的溢出問題。
C++語法
1.多繼承時(shí),子類的構(gòu)造函數(shù)應(yīng)該怎么寫?
如果父類里面有顯示的聲明構(gòu)造函數(shù),一般來說子類不需要再顯示的聲明構(gòu)造函數(shù),會生成一個(gè)默認(rèn)構(gòu)造函數(shù)。當(dāng)多繼承時(shí)可能會存在多義性,其實(shí)也很好理解,比如C繼承自類A和類B,而類A和類B都有相同的構(gòu)造函數(shù)(參數(shù)列表一樣),那么這個(gè)時(shí)候如果不在類C里面顯示的聲明一個(gè)新的構(gòu)造函數(shù),則編譯器會提示“C::C”不明確,即系統(tǒng)不知道去默認(rèn)調(diào)用A還是B的構(gòu)造函數(shù)。
2.類中的靜態(tài)成員到底是什么屬性?
常常會見到,類里面有一些靜態(tài)成員的聲明,其實(shí)static作用在類里面,說明這個(gè)成員是屬于類的,使用時(shí),需要加域名空間才能使用。與不在類中的static類似,比如在.cpp中定義一個(gè)static的成員,不管是靜態(tài)變量,還是靜態(tài)函數(shù),都只能在本cpp里面可見。有一點(diǎn)區(qū)別是,類中的靜態(tài)成員,在使用之前需要使用 如下方法初始化
3.派生類的構(gòu)造函數(shù)與析構(gòu)函數(shù)的調(diào)用順序
class A { //構(gòu)造和析構(gòu)在此省略 } class B { //構(gòu)造和析構(gòu)在此省略 } class C:public A,public B { //子類的構(gòu)造和析構(gòu) }利用斷點(diǎn)調(diào)式逐步執(zhí)行就能發(fā)現(xiàn)執(zhí)行順序是
A的構(gòu)造函數(shù)執(zhí)行
B的構(gòu)造函數(shù)執(zhí)行
C的構(gòu)造函數(shù)執(zhí)行
C的析構(gòu)函數(shù)執(zhí)行
B的析構(gòu)函數(shù)執(zhí)行
A的析構(gòu)函數(shù)執(zhí)行
規(guī)律是,無論繼承是什么順序,衍生類的構(gòu)造函數(shù)都是最后執(zhí)行,A和B的構(gòu)造函數(shù)是誰繼承在前誰先執(zhí)行
4.多態(tài)(類型轉(zhuǎn)換)
基類的指針可以指向派生類的對象,編譯器會隱式的執(zhí)行這種派生類到基類的轉(zhuǎn)換。之所以能轉(zhuǎn)換是因?yàn)槊恳粋€(gè)派生類都包含一個(gè)基類的全部。
5.虛繼承
考慮一種情況,基類A被A1和A2同時(shí)繼承,B繼承了A1和A2,此時(shí)A就會同時(shí)被繼承兩次(這樣說可能不妥),所以在訪問變量時(shí),可能會存在一些二意性,而且兩個(gè)基類也比較占空間。這時(shí)候就需要虛基類的出現(xiàn)了。虛基類無論被何種方式繼承,都只會被初始化一次,且基類的初始化需要孫子類或者更下級的類來完成初始化。
6.類型轉(zhuǎn)換構(gòu)造函數(shù)
簡單點(diǎn)來說,類型轉(zhuǎn)換構(gòu)造函數(shù)就是帶有一個(gè)參數(shù)的構(gòu)造函數(shù),具體實(shí)現(xiàn)例子如下
如果構(gòu)造函數(shù)前面加上explicit(明確的)可以阻止系統(tǒng)進(jìn)行改類型轉(zhuǎn)換。
7.成員/虛/靜態(tài)函數(shù)指針 成員/靜態(tài)變量指針
class MyT { public:void Test1(){cout << "普通成員函數(shù)調(diào)用" << endl;}static void Test2(){cout << "靜態(tài)成員函數(shù)調(diào)用" << endl;}virtual void Test3(){cout << "虛成員函數(shù)調(diào)用" << endl;}public:int m_a; //普通成員變量static int m_b; };int MyT::m_b = 1;int main() { MyT t;MyT * tp = &t;//1.普通成員函數(shù)//普通成員函數(shù)是跟著類走的,不是跟著對象走,只要有類,就能取到類成員函數(shù)的指針void (MyT::*myp)(void);myp = &MyT::Test1; (t.*myp)(); //前面必須拿圓括號括上,因?yàn)楹竺娴睦ㄌ杻?yōu)先級高!(tp->*myp)();//2.虛函數(shù) 與成員函數(shù)一致 必須綁定到類對象上void (MyT::*myp2) (void);myp2 = &MyT::Test3; (t.*myp2)();//3.靜態(tài)成員函數(shù)//不需要綁定到類對象上,可直接使用void (*myp3) (void);myp3 = &MyT::Test2;(*myp3)();//4.成員變量指針 不是某個(gè)地址 而是該成員變量與該類對象之間的偏移量int MyT::*myp4 = &MyT::m_a;//當(dāng)生成類對象時(shí),如果這個(gè)類中有虛函數(shù)表,就會有一個(gè)指向這個(gè)虛函數(shù)表的指針,這個(gè)指針占四個(gè)字節(jié)//所以這里看到的m_a的偏移是4,如果把 virtual 函數(shù)注釋后調(diào)試模式下查看myp4的值就會是0而不是0x4t.*myp4 = 111; // 等價(jià)于 t.m_a = 111;std::cout << t.m_a << endl;//靜態(tài)成員變量int *myp5 = &MyT::m_b;*myp5 = 111;return 0; }8.字節(jié)對齊問題
定義一個(gè)結(jié)構(gòu)體如下
按照常規(guī)的理解,其sizeof的結(jié)果應(yīng)該是11個(gè)字節(jié),但是我在64位機(jī)器上sizeof出來是24?為什么呢?看一下內(nèi)存分布
0x0113FBFC是對象的初始地址,可以看到A和B分別占用了一個(gè)字,D占用了八個(gè)字節(jié),后面的C也占用了8個(gè)字節(jié),最后末尾還有7個(gè)字節(jié)。首先看看百度百科對于字節(jié)對齊規(guī)則的描述
1) 結(jié)構(gòu)體變量的首地址能夠被其最寬基本類型成員的大小所整除;
2) 結(jié)構(gòu)體每個(gè)成員相對于結(jié)構(gòu)體首地址的偏移量都是成員大小的整數(shù)倍,如有需要編譯器會在成員之間加上填充字節(jié)(?);
3) 結(jié)構(gòu)體的總大小為結(jié)構(gòu)體最寬基本類型成員大小的整數(shù)倍,如有需要編譯器會在最末一個(gè)成員之后加上填充字節(jié)。(最后7個(gè)字節(jié)的來源)
在這個(gè)結(jié)構(gòu)體的定義前后加上以下定義,就會發(fā)現(xiàn)sizeof大小為11,說明取消了默認(rèn)的字節(jié)對齊。這里這個(gè)語句的意思就是,接下來的結(jié)構(gòu)體定義,都按照n字節(jié)對齊,除去所有的padding字節(jié)。使內(nèi)存更加緊湊,節(jié)約內(nèi)存。
#pragma pack(push,1) typedef struct Student {char A; char B; int64_t D;char C; }; #pragma pack(pop)9.malloc庫函數(shù)可能出現(xiàn)的問題以及改進(jìn)方法?
malloc是線程不安全的,有可能出現(xiàn)分配異常的問題,自定義一個(gè)函數(shù)
10.new 和 delete的使用
new和delete成對使用
且 new [] 與delete[]應(yīng)該對應(yīng)使用
如果是內(nèi)部數(shù)據(jù)類型 如 int char操作時(shí)不會內(nèi)存泄漏,但是是自定義的類如果在new時(shí)用new[] 但是delete時(shí)用delete,則會出現(xiàn)內(nèi)存泄漏。多出的內(nèi)存是用來存儲在初始化時(shí)分配了幾個(gè)數(shù)組元素,一般多出的內(nèi)存是4個(gè)字節(jié)。
自定義的類如果沒有自定義的析構(gòu)函數(shù),那么用delete也不會有內(nèi)存泄漏。
為什么像 int 這種內(nèi)部數(shù)據(jù)類型不用額外的4字節(jié)?
11.智能指針自定義刪除器
//shared_ptr
自定義刪除器方式1
std::shared_ptr<Socket> ptsocket(new Socket(),[](Socket * socket){socket->close();delete socket;})自定義刪除其方式2
std::unique_ptr<Socket, void(*)(Socket * socket)> ptsocket(new Socket(), [](Socket * socket) { //to do });自定義刪除器方式3
auto deletor = [](Socket* pSocket) {//關(guān)閉句柄pSocket->close();//TODO: 你甚至可以在這里打印一行日志...delete pSocket;};//std::unique_ptr<Socket, void(*)(Socket * pSocket)> spSocket(new Socket(), deletor);std::unique_ptr<Socket, decltype(deletor)> spSocket(new Socket(), deletor);后面的delete函數(shù)用Lambda表達(dá)式實(shí)現(xiàn)了
shared_ptr和unique_ptr的刪除器實(shí)現(xiàn)方式不一樣,在傳遞模板參數(shù)時(shí),unique_ptr還需要傳遞一個(gè)刪除器的類型,因此類實(shí)現(xiàn)也就不一樣,會導(dǎo)致不同刪除器類型的unique_ptr不能放置于同一個(gè)容器中。而shared_prt則不一樣,只要指向的對象一樣,即使刪除器不一樣,也能放置于同一個(gè)容器中。
詳解:王建偉C++ 5-7 00:30:32
12.對象的生命周期
以前一直覺得,函數(shù)執(zhí)行結(jié)束了,對象的生命周期才會結(jié)束,但是最近因?yàn)榭匆粋€(gè)bug才尼瑪知道,if while do while () 這些東西里面聲明的對象在出其作用域的時(shí)候都會釋放掉。
以上程序在作用外均無法使用對象,對象與函數(shù)出棧時(shí)一樣,都已經(jīng)被釋放掉了…
13.指針的長度
普通的裸指針是 四字節(jié)
shared_ptr 與 weak_ptr 是八個(gè)字節(jié),多出來的四個(gè)字節(jié)是用來存放指向控制塊的指針(包括 強(qiáng)引用計(jì)數(shù) 弱引用計(jì)數(shù) 自定義刪除器指針),實(shí)際上智能指針也就是存放了兩個(gè)裸指針。
另外 unique_ptr 的尺寸與前兩個(gè)都不一樣
unique_ptr p1(new string(“hello world!”));
sizeof(p1) // 輸出結(jié)果是4 。。
為什么unique_ptr是四個(gè)字節(jié)呢?因?yàn)闆]有自定義刪除器,如果增加了自己的刪除器,則 unique_ptr 的尺寸可能增加,比如傳遞函數(shù)指針類型時(shí)
14.裸指針初始化只能指針陷阱
利用裸指針同時(shí)初始化兩個(gè)智能指針,會發(fā)現(xiàn)p1和p2的強(qiáng)引用都是1,釋放時(shí)會釋放兩次。
15.循環(huán)引用導(dǎo)致的智能指針無法釋放
new了兩個(gè)對象,等于申請了兩塊內(nèi)存,但是pca的成員pcb是利用pcb創(chuàng)建的,會導(dǎo)致pca的強(qiáng)引用加1,同理 pcb的成員pca是利用pca創(chuàng)建的,會導(dǎo)致pcb的強(qiáng)引用也加1,所以在pca和pcb退出作用域時(shí),不會釋放其指向的內(nèi)存。
15.智能指針設(shè)計(jì)的目的以及auto_ptr被放棄的原因
防止內(nèi)存泄漏,休業(yè)式RAII的一種提現(xiàn)。
auto_ptr使用缺陷是
這里定義會導(dǎo)致p1自動被釋放,造成不可預(yù)計(jì)的問題
雖然unique_ptr也是獨(dú)占式的指針,但是unique_ptr會在編譯時(shí)報(bào)錯(cuò),編譯器不允許存在這種操作,避免后續(xù)使用p1導(dǎo)致的程序崩潰問題。
16.死鎖的出現(xiàn)以及規(guī)避的方法
同一個(gè)線程存在對兩個(gè)及兩個(gè)以上的鎖同時(shí)操作時(shí),有可能出現(xiàn)死鎖的情況。比較簡單的方案就是保證調(diào)用的順序來保證不出現(xiàn)死鎖的情況,另外,std下提供了lock方法,如
這個(gè)方法會一直嘗試獲取1鎖與2鎖,如果獲取不到又會釋放等待下一次獲取。
上述方法有一點(diǎn)不好的就是沒有用到lock_guard,如下可以解決該問題。
使用adopt_lock關(guān)鍵字會使得guard對象在生成時(shí)不直接調(diào)用加鎖,而且后面也不用手動釋放鎖,脫離作用域自動釋放。
17.為什么字符串在網(wǎng)絡(luò)傳輸不需要大小端轉(zhuǎn)換,而像int/long…等卻需要轉(zhuǎn)換?
首先
大端模式,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的低地址中
小端模式,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的高地址中
大端模式與人類的識別方式是相似的,從左往右,從高到底開始寫
大小端是指在一個(gè)字節(jié)內(nèi),數(shù)據(jù)存儲的不同順序,以32位小端為例
char a = 65; (字符A)a 在內(nèi)存中的存儲形式是
0x1000 0010 0000 0000
如果是大端模式,那么在內(nèi)存中的存儲形式是
0x0000 0000 0100 0001
//未驗(yàn)證 待驗(yàn)證
18.dynamic_cast 和 static_cast 的區(qū)別
static_cast屬于靜態(tài)類型的轉(zhuǎn)換,會在編譯的時(shí)候進(jìn)行類型檢查。
可以用于相關(guān)類型的轉(zhuǎn)換,比如int和double之間的轉(zhuǎn)換,子類轉(zhuǎn)成父類,void*和其他類型之間的轉(zhuǎn)換,一般不能用于指針類型之間的互轉(zhuǎn)。從我的角度來看,static_cast就比C的強(qiáng)制類型轉(zhuǎn)換的作用稍微弱一點(diǎn),加了一些類型之間轉(zhuǎn)換的限制。
dynamic_cast主要是具有運(yùn)行時(shí)檢查功能,轉(zhuǎn)換也是在運(yùn)行時(shí)轉(zhuǎn)換的,比如有三個(gè)類 基類是 human man 和women類分別繼承自 human
有如下轉(zhuǎn)換,而且human必須具有虛函數(shù),否則dynamic_cast無法正常使用,只有虛函數(shù)的存在,才使用指針或引用所指向?qū)ο蟮膭討B(tài)類型。基類有虛函數(shù)之后,會有一個(gè)指針指向基類的虛函數(shù)表,
以上代碼在轉(zhuǎn)換時(shí),將A轉(zhuǎn)換成 women* 時(shí),因?yàn)閐ynamic_cast 屬于具有運(yùn)行時(shí)檢查功能,他發(fā)現(xiàn)A 指針實(shí)際上不是指向的一個(gè)women類時(shí),轉(zhuǎn)換就會失敗,得到的C是一個(gè)空指針。
另外還有兩種強(qiáng)制類型轉(zhuǎn)換
const_cast 和 reinterpret_cast
const_cast 用于去除指針或者引用的常量屬性,屬于編譯時(shí)類型轉(zhuǎn)換。
常見錯(cuò)誤使用
reinterpret_cast 編譯時(shí)進(jìn)行的類型轉(zhuǎn)換
重新解釋,把操作數(shù)的類型解釋為另外一種類型,處理無關(guān)類型的轉(zhuǎn)換。
比如下面這個(gè)轉(zhuǎn)換,除了C里面的強(qiáng)制類型轉(zhuǎn)換能做到,其他的dynamic_cast static_cast const_cast均無法實(shí)現(xiàn)轉(zhuǎn)換,編譯無法通過。
19.引用和指針有何區(qū)別?怎么拿到引用變量的地址?
引用和指針本無區(qū)別,只是編譯器為了方便設(shè)置的語法糖,引用本身也是存儲了指向?qū)ο蟮闹羔樧兞?#xff0c;和 利用 & 取到的變量地址沒有任何差別,只是看起來不一樣。
至于怎么拿地址?似乎引用變量沒有占內(nèi)存啊?
參考文
引用比指針安全么?也不是,一樣也存在安全問題,僅僅就是一個(gè)語法糖而已
為了進(jìn)一步驗(yàn)證引用與指針在本質(zhì)上的相同,我們看當(dāng)引用作為函數(shù)參數(shù)傳遞時(shí),編譯器的行為:
1 void Swap(int& v1, int& v2); 2 void Swap(int* v1, int* v2);3 4 int var1 = 1; 5 00A64AF8 mov dword ptr [var1],1 6 int var2 = 2; 7 00A64AFF mov dword ptr [var2],2 8 Swap(var1,var2); 9 00A64B06 lea eax,[var2] 10 00A64B09 push eax 11 00A64B0A lea ecx,[var1] 12 00A64B0D push ecx 13 00A64B0E call Swap (0A6141Fh) 14 00A64B13 add esp,8 15 Swap(&var1, &var2); 16 00A64B16 lea eax,[var2] 17 00A64B19 push eax 18 00A64B1A lea ecx,[var1] 19 00A64B1D push ecx 20 00A64B1E call Swap (0A61424h) 21 00A64B23 add esp,8上面代碼再次證明了,引用與指針的行為完全一致,只是編譯器在編譯時(shí)對引用作了更嚴(yán)格的限制。
不要用匯編結(jié)果來替代概念,引用不占空間意思就是不占對象空間,不表示不占指針的少量空間。實(shí)際上指針是匯編工具實(shí)現(xiàn)引用的一種方式而已,而有的優(yōu)化結(jié)果可能沒有代表自己的指針。
總而言之,引用就是引用,是這種概念,它為方便程序員使用,和方便匯編工具優(yōu)化而產(chǎn)生。匯編怎么實(shí)現(xiàn)和優(yōu)化是匯編的事,至于出了什么違反該概念的結(jié)果,是匯編的錯(cuò),而不是定義的錯(cuò),不要本末倒置。
你可以通過匯編來了解編譯器怎樣實(shí)現(xiàn)引用
引用 卻不應(yīng)該用匯編來解釋 它只是一個(gè)概念
贊同,引用只是編譯器之上,給出來的一個(gè)抽象定義。接口的實(shí)現(xiàn),由編譯器來決定!
仔細(xì)想想,確實(shí)如此,引用只是一個(gè)概念,為我們提供了一個(gè)接口。怎么實(shí)現(xiàn),由編譯器自己決定。
20.RTTI是什么東西?
run time type identification :運(yùn)行時(shí)類型檢查
利用dynamic_cast將基類的指針或引用安全的轉(zhuǎn)換成子類的指針或者引用,并用來調(diào)用子類的某個(gè)重寫的虛函數(shù)。
21.獲取開機(jī)到現(xiàn)在的時(shí)間函數(shù)
extern int clock_gettime (clockid_t __clock_id, struct timespec *__tp) __THROW;22.git相關(guān)
撤銷已a(bǔ)dd的單個(gè)文件修改到modified
撤銷已commit的某個(gè)文件的修改
首先查詢這個(gè)文件的log:
其次查找到這個(gè)文件的上次commit id xxx,并對其進(jìn)行reset操作:
git reset <commit-id> <fileName>再撤銷對此文件的修改:
git checkout <fileName>最后amend一下,再push上去:
總結(jié)
- 上一篇: 首届中国云南普洱茶茶王获奖名单及常识
- 下一篇: Spark机器学习实验