深信服面經

字符複製函數:

http://www.javashuo.com/article/p-mgfhdvvh-ct.htmlhtml

問題主要有兩個:node

1.緩衝區溢出:若是src的長度大於dst的長度,則將賦值到dst的\0外,形成緩衝區溢出。程序員

2.內存重疊:若是dst位於src和src+strlen(src)之間,則若是從頭開始複製會形成內存重疊,若是以\0爲終止的方式複製則永遠不會中止下來編程

strcpy,strncpy,strlcpy:安全

strcpy:服務器

傳入 dst和src,以\0爲終止,可能形成緩衝區溢出網絡

strncpy:數據結構

複製n個字符到dst區域,結尾不加\0.併發

strlcpy:socket

strcpy的安全版本,傳入參數爲dst,src和sizeof(dst)。若是src大於dst的長度,則會截斷爲sizeof(dst)大小而防止緩衝區溢出。

內存對齊:

https://www.douban.com/group/topic/128339995/

1.數據成員的對齊:放置在min(數據成員長度,默認對齊長度)的整數倍位置

2.結構體總體對齊:補齊min(最大數據成員長度,默認對齊長度)的整數倍位置

string的實現

http://www.javashuo.com/article/p-msyujwrn-cb.html

如何向其餘進程發信號

kill函數

UDP使用connect,bind:

UDP是一個無鏈接的協議,所以socket函數connect彷佛對UDP是沒有意義的, 然而事實不是這樣。 一個插口有幾個屬性,其中包括協議,本地地址/端口,目的地址/端口。 對於UDP來講,socket函數創建一個插口;bind函數指明瞭本地地址/端口 (包括ADDR_ANY,通配全部本地網絡接口);connect能夠用來指明目的地 址/端口; 通常來講,UDP客戶端在創建了插口後會直接用sendto函數發送數據,須要 在sendto函數的參數裏指明目的地址/端口。若是一個UDP客戶端在創建了插 口後首先用connect函數指明瞭目的地址/端口,而後也能夠用send函數發送 數據,由於此時send函數已經知道對方地址/端口,用getsockname也能夠得 到這個信息。 UDP客戶端在創建了插口後會直接用sendto函數發送數據,還隱含了一個操做, 那就是在發送數據以前,UDP會首先爲該插口選擇一個獨立的UDP端口(在1024 -5000之間),將該插口置爲已綁定狀態。若是一個UDP客戶端在創建了插口後 首先用bind函數指明瞭本地地址/端口,也是能夠的,這樣能夠強迫UDP使用指 定的端口發送數據。(事實上,UDP無所謂服務器和客戶端,這裏的界限已經模 糊了。) UDP服務器也可使用connect,如上面所述,connect能夠用來指明目的地址 /端口;這將致使服務器只接受特定一個主機的請求。

socket相關函數

TCP編程:併發服務器

服務器端:

socket( int af, int type, int protocol);

af爲ip協議種類(ipv4/ipv6),type爲協議(數據流/用戶數據報)

int bind(int sockfd,  const struct sockaddr, socklen_t addrlen);

將主動套接字sockfd綁定到對應的端口,這樣監聽對應的端口

sockfd爲監聽套接字,sockaddr爲綁定的端口和IP,對服務器來講IP爲INADDR_ANY,即任意IP;端口爲要監聽的端口。addrlen爲sockaddr的長度

注意任何對sockaddr的寫入都要轉爲網絡字節序,對端口使用htons,對IP使用htonl.

int listen(int sockfd, int backlog);

listen將主動套接字轉換爲被動套接字,成功返回0,不然返回-1

sockfd爲主動套接字,backlog爲隊列長度

內核爲listendfd維護兩個隊列:

一個爲未完成鏈接隊列,用於保存那些正在完成三路握手的套接字,也就是說此隊列的套接字此時正處於SYN_RCVD狀態

一個爲已完成鏈接隊列,用於保存那些已經完成三路握手的套接字

兩個隊列長度和不能超過backlog

accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd爲監聽套接字,addr爲客戶端的信息,addrlen爲addr長度

返回一個已鏈接套接字

注意addr中存放的信息此時爲網絡字節序,要是用ntohl/ntohs來轉換

客戶端:

socket:同上

int connect(SOCKET s, const struct sockaddr * name, int namelen);

name填對端的信息,namelen爲name長度

connect後發起三次握手

UDP編程:迭代服務器

服務器端:

socket:同上

bind:同上

ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flags, struct sockaddr *from,socket_t *fromlen);

sockfd爲套接字,buf爲讀取的目的緩衝區,len爲緩衝區長度,from和fromlen爲對端信息

客戶端:

socket:同上

int sendto(int s, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen);

s爲套接字,msg爲要發送的緩衝區,len爲發送字節數,to和tolen保存要發送的對端的信息

判斷大小端

 ++i和i++在性能上的區別:

i++在執行過程當中產生了一個臨時變量,而++i並無。所以,在使用相似for循環這種要運用到自增時,推薦使用++i

給40億個不重複的unsigned int的整數,沒排過序的,而後再給一個數,如何快速判斷這個數是否在那40億個數當中

申請512M的內存

一個bit位表明一個unsigned int值

讀入40億個數,設置相應的bit位

讀入要查詢的數,查看相應bit位是否爲1,爲1表示存在,爲0表示不存在

strcmp

int strcmp(const char *str1,const char *str2)
{
    /*不可用while(*str1++==*str2++)來比較,當不相等時仍會執行一次++,
    return返回的比較值其實是下一個字符。應將++放到循環體中進行。*/
    while(*str1 == *str2)
    {
        assert((str1 != NULL) && (str2 != NULL));                
        if(*str1 == '\0')
            return 0;        
        str1++;
        str2++;
    }
    return *str1 - *str2;
}

 

重載,覆蓋,隱藏

重載
重載是指同名函數具備不一樣的參數表。

在同一訪問區域內聲明的幾個具備不一樣參數列表(參數的類型、個數、順序不一樣)的同名函數,程序會根據不一樣的參數列來肯定具體調用哪一個函數。

對於重載函數的調用,編譯期間肯定,是靜態的,它們的地址在編譯期間就綁定了。

重載不關心函數的返回值類型。

函數重載的特徵

相同的範圍(同一個類中)
函數名字相同
參數不一樣
virtual關鍵字無關緊要

覆蓋
覆蓋是指派生類中存在從新定義基類的函數,其函數名、參數列、返回值類型必須同父類中相應被覆蓋的函數嚴格一致,覆蓋函數和被覆蓋函數只有函數體不一樣,當基類指針指向派生類對象,調用該同名函數時會自動調用子類中的覆蓋版本,而不是父類中的被覆蓋函數版本。

函數調用在編譯期間沒法肯定,因虛函數表存儲在對象中,對象實例化時生成。所以,這樣的函數地址是在運行期間綁定。

覆蓋的特徵

不一樣的範圍(分別位於派生類和基類)
函數名字相同
參數相同
返回值類型相同
基類函數必須有virtual關鍵字。
重載和覆蓋的關係
覆蓋是子類和父類之間的關係,是垂直關係;重載是同一個類中方法之間的關係,是水平關係。

覆蓋只能由一對方法產生關係;重載是兩個或多個方法之間的關係。

覆蓋要求參數列表相同;重載要求參數列表不一樣。

覆蓋關係中,調用方法是根據對象的類型來決定的,重載關係是根據調用時的實參表與形參表來選擇方法體的。

隱藏
隱藏是指派生類的函數屏蔽了與其同名的基類函數。

若是派生類的函數與基類的函數同名,但參數不一樣,則不管有無virtual關鍵字,基類的函數都被隱藏。

若是派生類的函數與基類的函數同名,而且參數也相同,可是基類函數沒有virtual關鍵字,此時基類的函數被隱藏。

隱藏的特徵

必須分別位於基類和派生類中
必須同名
參數不一樣的時候自己已經不構成覆蓋關係了,因此此時有無virtual關鍵字不重要
參數相同時就要看是否有virtual關鍵字,有就是覆蓋關係,無就是隱藏關係

指針函數與函數指針:

指針函數:

指針函數,簡單的來講,就是一個返回指針的函數,其本質是一個函數,而該函數的返回值是一個指針。

int *fun(int x,int y);

函數指針:

int (*fun)(int x,int y);

函數指針是須要把一個函數的地址賦值給它,有兩種寫法:

fun = &Function;

fun = Function;

指針函數本質是一個函數,其返回值爲指針。
函數指針本質是一個指針,其指向一個函數。

計算浮點數的開方

咱們知道這個值的大小必定在1到2之間,因此採用二分的思想,left=1,right=2,而後不斷判斷mid來計算right-left>eps(eps=1e-5),直到小於這個數截止。

double f(double x)
{
    return x*x;
}
 
double calSqrt()
{
    double mid,left=1,right=2;
    while(right-left>eps)
    {
        mid=(left+right)/2;
        if(f(mid)>2)
            right=mid;
        else
            left=mid;
    }
    return mid;
}
 

  

union和struct的區別

1:共用體和結構體都是由多個不一樣的數據類型成員組成, 但在任何同一時刻, 共用體只存放一個被選中的成員, 而結構體則存放全部的成員變量。

2:對於共用體的不一樣成員賦值,將會對其餘成員重寫, 原來成員的值就不存在了, 而對於結構體的不一樣成員賦值是互不影響的

3:內存分配不一樣:union的大小爲其內部全部變量的最大值,按照最大類型的倍數進行分配大小

union的對齊:

typedef union
{
char c[10];
int i;
}u22;  

10個字節就足以裝下c或i了,可是對i要對齊,所以其實是12個字節。

最終聯合體的最小的size也要是所包含的全部類型的基本長度的最小公倍數才行

爲何要內存對齊

        一、平臺緣由(移植緣由)
             A   不是全部的硬件平臺都能訪問任意地址上的任意數據的
             B    某些硬件平臺只能在某些地址處取某些特定類型的數據,不然拋出硬件異常。
        二、性能緣由:
             A   數據結構(尤爲是棧)應該儘量地在天然邊界上對齊。
             B   緣由在於,爲了訪問未對齊的內存,處理器須要做兩次內存訪問;而對齊的內存訪問僅須要一次訪問。

cpu讀取數據時有一個粒度,好比當粒度爲4時,若是不進行內存對齊,則當取2~5字節的數據時,要先取0~3,在取4~7,最後拼接起來。而若是對齊,則直接就能取到這四個字節,由於他們放在cpu恰好能一次取完的四個字節的位置

read的返回值

一、若是讀取成功,則返回實際讀到的字節數。這裏又有兩種狀況:

一是若是在讀完count要求字節以前已經到達文件的末尾,那麼實際返回的字節數將 小於count值,可是仍然大於0;

二是在讀完count要求字節以前,仍然沒有到達文件的末尾,這是實際返回的字節數等於要求的count值
二、若是讀取時已經到達文件的末尾,則返回0。
三、若是出錯,則返回-1。

可重入函數與不可重入函數

可重入函數

一個函數在執行的過程當中被打斷,而後會再被從頭執行一次,執行完後,再回來把剛纔沒執行完的部分執行完。這就至關於嵌套的執行了。函數是公共代碼,這樣的執行是容許的。函數的執行能夠被打斷,打斷以後還能夠再從頭執行,執行完後接着執行剛纔沒有執行的代碼,而後第一次執行的代碼(被打斷的函數)執行結果仍是正確的。也就是說,這個函數執行,不管中間把這個函數再嵌入執行多少遍,怎麼嵌入,最終執行完都不會對函數內部功能/程序邏輯形成影響,而且執行的結果都是正確的,這樣的函數就是可重入函數。
經常使用的可重入函數:

  1. 不要使用全局變量,防止別的代碼覆蓋這些變量的值。 
  2. 調用這類函數以前先關掉中斷,調用完以後立刻打開中斷。防止函數執行期間被中斷進入別的任務執行。 
  3. 使用信號量(互斥條件)。 

總而言之:要保證中斷是安全的。

不可重入函數

函數執行期間,被中斷,從頭執行這個函數,執行完畢後再返回剛纔的中斷點繼續執行,此時因爲剛纔的中斷致使瞭如今重新在中斷點執行時發生了不可預料的錯誤。也就是說,若是函數在不一樣的地方/時序進行調用,會對函數的功能邏輯形成影響,這種函數就稱爲不可重入函數。

常見的不可重入函數:

  1. 使用了靜態數據結構
  2. 調用了malloc和free等
  3. 調用了標準I/O函數
  4. 進行了浮點運算 

malloc與free是不可重入的,它們使用了全局變量來指向堆區。標準I/O大多都使用了全局數據結構

TCP粘包,拆包

1.服務端分2次讀取到了兩個獨立的包,分別是D1,D2,沒有粘包和拆包;

2.服務端一次性接收了兩個包,D1和D2粘在一塊兒了,被成爲TCP粘包;

3.服務端分2次讀取到了兩個數據包,第一次讀取到了完整的D1和D2包的部份內容,第二次讀取到了D2包的剩餘內容,這被稱爲拆包;

粘包、拆包發生緣由
發生TCP粘包或拆包有不少緣由,現列出常見的幾點,可能不全面,歡迎補充,
一、要發送的數據大於TCP發送緩衝區剩餘空間大小,將會發生拆包。
二、待發送數據大於MSS(最大報文長度),TCP在傳輸前將進行拆包。
三、要發送的數據小於TCP發送緩衝區的大小,TCP將屢次寫入緩衝區的數據一次發送出去,將會發生粘包。
四、接收數據端的應用層沒有及時讀取接收緩衝區中的數據,將發生粘包。

粘包、拆包解決辦法
經過以上分析,咱們清楚了粘包或拆包發生的緣由,那麼如何解決這個問題呢?解決問題的關鍵在於如何給每一個數據包添加邊界信息,經常使用的方法有以下幾個:
一、發送端給每一個數據包添加包首部,首部中應該至少包含數據包的長度,這樣接收端在接收到數據後,經過讀取包首部的長度字段,便知道每個數據包的實際長度了。
二、發送端將每一個數據包封裝爲固定長度(不夠的能夠經過補0填充),這樣接收端每次從接收緩衝區中讀取固定長度的數據就天然而然的把每一個數據包拆分開來。
三、能夠在數據包之間設置邊界,如添加特殊符號,這樣,接收端經過這個邊界就能夠將不一樣的數據包拆分開。

殭屍進程

危害:

在每一個進程退出的時候,內核釋放該進程全部的資源,包括打開的文件,佔用的內存等。可是仍然爲其保留必定的信息(包括進程號the process ID,退出狀態the termination status of the process,運行時間the amount of CPU time taken by the process等)。直到父進程經過wait / waitpid來取時才釋放. 但這樣就致使了問題,若是進程不調用wait / waitpid的話,那麼保留的那段信息就不會釋放,其進程號就會一直被佔用,可是系統所能使用的進程號是有限的,若是大量的產生殭屍進程,將由於沒有可用的進程號而致使系統不能產生新的進程. 此即爲殭屍進程的危害,應當避免。

避免:

⒈父進程經過wait和waitpid等函數等待子進程結束,這會致使父進程掛起。
⒉ 若是父進程很忙,那麼能夠用signal函數爲SIGCHLD安裝handler,由於子進程結束後, 父進程會收到該信號,能夠在handler中調用wait回收。
⒊ 若是父進程不關心子進程何時結束,那麼能夠用signal(SIGCHLD,SIG_IGN) 通知內核,本身對子進程的結束不感興趣,那麼子進程結束後,內核會回收, 並再也不給父進程發送信號。
⒋ 還有一些技巧,就是fork兩次,父進程fork一個子進程,而後繼續工做, 子進程fork一 個孫進程後退出,那麼孫進程被init接管,孫進程結束後,init會回收。不過子進程的回收 還要本身作。

孤兒進程

一個父進程退出,而它的一個或多個子進程還在運行,那麼那些子進程將成爲孤兒進程。孤兒進程將被init進程(進程號爲1)所收養,並由init進程對它們完成狀態收集工做。孤兒進程是沒有父進程的進程,孤兒進程這個重任就落到了init進程身上,init進程就好像是一個民政局,專門負責處理孤兒進程的善後工做。每當出現一個孤兒進程的時候,內核就把孤 兒進程的父進程設置爲init,而init進程會循環地wait()它的已經退出的子進程。這樣,當一個孤兒進程淒涼地結束了其生命週期的時候,init進程就會表明黨和政府出面處理它的一切善後工做。所以孤兒進程並不會有什麼危害

main以前的操做

1.設置棧指針

2.初始化static靜態和global全局變量,即data段的內容

3.將未初始化部分的賦初值:數值型short,int,long等爲0,bool爲FALSE,指針爲NULL,等等,即.bss段的內容

4.運行全局構造器,估計是C++中構造函數之類的吧

5.將main函數的參數,argc,argv等傳遞給main函數,而後才真正運行main函數

extern C的做用

  extern "C"的主要做用就是爲了可以正確實現C++代碼調用其它C語言代碼。

  加上extern 「C」後,會指示編譯器將這部分代碼按C語言進行編譯,而不是C++的。這是由於C++支持函數重載,所以,編譯器在編譯函數的過程當中會將函數參數類型也加到編譯後的代碼中,而不只僅是函數名;而C語言不支持函數重載,所以編譯C語言代碼的函數時不會帶上函數的參數類型,通常只包括函數名。

  extern "C"包含兩層含義:

  1. extern關鍵字告訴編譯器其聲明的函數和變量能夠在本模塊或其餘模塊中使用。

    在模塊的頭文件中對本模塊提供給其餘模塊引用的函數和全局變量以關鍵字extern生命。與extern對應的是static,static代表變量或函數只能在本模塊中使用,所以,被static修飾的變量或函數是不可能被extern 「C」修飾的。

  2. 「C」表示編譯器在編譯時會按照C編譯器的方式進行編譯。

地址空間佈局

http://www.javashuo.com/article/p-dwtgqnsn-u.html

堆和棧的區別

1)管理方式:對於棧來說,是由編譯器自動管理,無需咱們手工控制;對於堆來講,釋放工做由程序員控制,容易產生內存泄露

2)空間大小:通常來說在32位系統下,堆內存能夠達到3G的空間(4G有1G要給內核);而棧的最大容量是事先規定好的(例如,在VC6下面,默認的棧空間大小是1M,可修改)

3)碎片問題:對於堆來說,頻繁的new/delete勢必會形成內存空間的不連續,從而形成大量的碎片,使程序效率下降;對於棧來說,則不會存在碎片,由於棧是先進後出的結構,一個內存塊要從棧中彈出,在它上面的後進的內存塊確定要先被彈出

4)生長方向:對於堆來說,生長方向是向上的,也就是向着內存地址增長的方向;對於棧來說,它的生長方向是向下的,是向着內存地址減少的方向增加;

5)分配方式:堆都是動態分配的;而棧有2種分配方式:靜態分配和動態分配,靜態分配是編譯器完成的,好比局部變量的分配,動態分配由alloca函數(相似於malloc,專門在棧中申請空間的函數)進行分配,可是即便是動態分配,它也和堆是不一樣,棧的動態分配是由編譯器進行釋放,無需咱們手工釋放

6)分配效率:棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高;堆則是C/C++函數庫提供的,它的機制是很複雜,例如:爲了分配一塊內存,庫函數會在堆內存中搜索連續的足夠大小的空間,若是沒有足夠大的空間(多是因爲內存碎片太多),就須要操做系統從新整理內存空間,這樣就有機會分到足夠大的內存,而後進行返回。顯然,堆的效率比棧要低得多

標準IO和文件IO的區別(fread和read的區別/fwrite和write的區別)

文件IO跟標準IO的區別

一、  經過系統IO讀寫文件時,每次操做都會執行相關係統調用。這樣處理的好處是直接讀寫實際文件,壞處是頻繁的系統調用會增長系統開銷。標準IO能夠當作是在文件IO的基礎上封裝了緩衝機制先讀寫緩衝區,必要時再訪問實際文件,從而減小了系統調用的次數。

二、  文件IO中用文件描述符表現一個打開的文件,能夠訪問不一樣類型的文件,如普通文件、設備文件和管道文件等。而標準IO中用FILE(流)表示一個打開的文件,一般只用來訪問普通文件。
標準IO能夠當作是在文件IO的基礎上封裝了緩衝機制:

讀時:read是系統調用,每次要讀幾個字節就讀幾個字節。而fread則會多讀一些字節到緩衝區,這樣下次就不用調用系統調用去讀了。好比讀4byte,read就只會讀4byte.而fread可能會讀4K到本身的緩衝區,這樣下次再要讀4byte時,read就仍要去內核讀,而fread則直接在本身的緩衝區中去讀取。

寫時:write也是系統調用,每次要寫幾個字節就寫幾個字節。而fwrite則會將內容先寫到本身的緩衝區,到滿了或者fflush時再寫入內核緩衝區,這樣至關於節省了不少次write系統調用。

malloc最大申請多大的內存空間

地址空間限制是有的,可是malloc一般狀況下申請到的空間達不到地址空間上限。內存碎片會影響到你「一次」申請到的最大內存空間。好比你有10M空間,申請兩次2M,一次1M,一次5M沒有問題。但若是你申請兩次2M,一次4M,一次1M,釋放4M,那麼剩下的空間雖然夠5M,可是因爲已經不是連續的內存區域,malloc也會失敗。系統也會限制你的程序使用malloc申請到的最大內存。Windows下32位程序若是單純看地址空間能有4G左右的內存可用,不過實際上系統會把其中2G的地址留給內核使用,因此你的程序最大能用2G的內存。除去其餘開銷,你能用malloc申請到的內存只有1.9G左右

Linux內存分配

https://blog.csdn.net/gfgdsg/article/details/42709943

兩個同樣的玻璃球,若是從相同的樓摔下去都會碎,假設選擇有一到一百層樓,問用最少的次數判斷玻璃球從哪一層下去會碎?

兩個玻璃球,第一個玻璃球用於分段,第二個玻璃球用於找到具體的樓層:

好比,第一個球在35層碎了,則咱們從第一層開始逐漸往上增長到35層,當某一層碎了,這一層就恰好是碎的那一層。

而若是在35層沒有碎,則從36層逐漸往上增長,直到某一層碎了,則這一層恰好是碎的那一層。

所以第一個球實際上用來找到臨界段,第二個球用於找到具體的層數。

如今的問題時,因爲是求最壞時間複雜度,也就是說若是在第99層才碎,則要試99次。

如今要找到一個均勻分佈的方法,使這個球不管在哪層碎,最終使用的次數都同樣。

假設總共要試k次:

若是要試k次,則當第一個球在第k層碎掉,這裏花了一次。第二個球在1~k-1層中找打具體的樓層的最壞複雜度爲k-1,則總共的複雜度爲k

而若是不在第k層碎掉,則還能夠用第一個球去找臨界段,但已經扔了一次了,所以若是還要保證最後爲k,則應該從k層往上數k-1層。即在第k+k-1層去扔第一個球,這樣此時:

第一個球在第k層扔一次花了1次,若是在第k+k-1層碎了,這時又花了1次,而第二個球在k+1~k+k-1共花了k-2次,則在第k+k-1層壞掉,也是1+1+k-2=k次。

同理,若是k+k-1層也沒碎,則要在第k+k-1+k-2去試。。。。

最後要求k+k-1+k-2+...+2+1>=99。99是由於若是99都沒碎,則必定在100層碎了。

解得k=14.

這樣,若是在第14層碎了,花費了14次

而若是在第27層碎了,花費了:14層一次,27層一次,15~26共12次,1+1+12=14,這樣也是14次。

以此類推,不管在14,仍是27,仍是39層碎,都只須要14次就能找到對應層。

哈夫曼樹

https://blog.csdn.net/xueba8/article/details/78477892

KMP

pthread_join與pthread_detach

pthread_join()便是子線程合入主線程,主線程阻塞等待子線程結束,而後回收子線程

pthread_detach()即主線程與子線程分離,子線程結束後,資源自動回收

pthread有兩種狀態joinable狀態和unjoinable狀態,若是線程是joinable狀態,當線程函數本身返回退出時或pthread_exit時都不會釋放線程所佔用堆棧和線程描述符(總計8K多)

C++哪些函數不能聲明爲虛函數

1)普通函數

普通函數不屬於成員函數,是不能被繼承的

2)友元函數

友元函數不屬於類的成員函數,不能被繼承

3)構造函數

首先說下什麼是構造函數,構造函數是用來初始化對象的。假如子類能夠繼承基類構造函數,那麼子類對象的構造將使用基類的構造函數,而基類構造函數並不知道子類的有什麼成員,顯然是不符合語義的。從另一個角度來說,多態是經過基類指針指向子類對象來實現多態的,在對象構造以前並無對象產生,所以沒法使用多態特性,這是矛盾的。所以構造函數不容許繼承。

4)內聯成員函數

咱們須要知道內聯函數就是爲了在代碼中直接展開,減小函數調用花費的代價。也就是說內聯函數是在編譯時展開的。而虛函數是爲了實現多態,是在運行時綁定的。所以顯然內聯函數和多態的特性相違背。

5)靜態成員函數

首先靜態成員函數理論是可繼承的。可是靜態成員函數是編譯時肯定的,沒法動態綁定,不支持多態,所以不能被重寫,也就不能被聲明爲虛函數。

內聯函數、靜態函數和普通函數之間的區別?

答:

1.內聯函數和普通函數最大的區別在於內部的實現方面,當普通函數在被調用時,系統首先跳躍到該函數的入口地址,執行函數體,執行完成後,再返回到函數調用的地方,函數始終只有一個拷貝; 而內聯函數則不須要進行一個尋址的過程,當執行到內聯函數時,此函數展開(很相似宏的使用),若是在 N處調用了此內聯函數,則此函數就會有N個代碼段的拷貝。

2.static函數和普通函數的最大的區別在於做用域方面,static函數限定在本源碼文件中,不能被本源碼文件之外的代碼文件調用。而普通的函數,默認是extern的,也就是說,能夠被其它代碼文件調用該函數。同時static函數在內存中只有一份,普通函數在每一個被調用中維持一份拷貝。

夥伴系統

若是分頁來避免碎片,則效率太低:

好比若是分頁大小爲4KB,則對一個16KB的內存來講,每進行到下一個4KB的內存都會觸發缺頁錯從而致使頁面置換,這樣16KB中的4個頁都要進行MMU映射,效率過低。

Linux內存分配——夥伴系統

目的: 最大限度的下降內存的碎片化。
原理:
1.將 內存塊分爲了11個連續的頁框塊(1,2,4,8....512,1024),其中 每個頁框塊中用鏈表將內存塊對應內存大小的塊進行連接。
2.若須要一塊256大小的內存塊,則從對應的256鏈表中查找空餘的內存塊,如有則分配。不然查找512等等。
3.若在256中未找到空餘內存塊,在512中查找到空餘的內存塊。則將512分紅兩部分,一部分進行分配,另外一部分則插入256鏈表中。
 4.內存的釋放過程與分配過程相反。在分配過程當中由大塊分解而成的小塊中沒有被分配的塊將一直等着被分配的塊被釋放,從而和其合併。最終至關於沒有劃分小塊。
總結: 夥伴系統在分配和釋放的過程當中執行互逆的過程,其將會極大力度的抵消碎片的產生

inode

http://www.javashuo.com/article/p-hbtbestk-p.html

inode是什麼?

理解inode,要從文件儲存提及。文件儲存在硬盤上,硬盤的最小存儲單位叫作「扇區」。每一個扇區能儲存512字節(至關於0.5KB)

操做系統在讀取硬盤的時候,不會一個個扇區的讀取,這樣效率過低,而是一次性連續讀多個扇區,即一次性讀取一個「塊」(block)。這種由多個扇區組成的「塊」,是文件存取的最小單位。「塊」的大小,最多見的是4kb,即連續八個sector組成一個block。

文件數據都存放在block中,那麼很顯然,咱們還必須找到一個地方儲存文件的元信息,好比文件的建立者、文件的建立日期、文件的大小等等。這種儲存文件元信息的區域就叫作inode,中文譯名爲「索引節點」

每個文件都有對應的inode,裏面包含了與該文件有關的一些信息。

inode的內容

inode包含文件的元信息,具體來講有如下內容:

  • 文件的字節數
  • 文件的擁有者uid
  • 文件的所屬組gid
  • 文件的r、w、x權限
  • 文件的時間戳
    • ctime:文件的inode上一次變更的時間
    • mtime:文件內容上一次變更的時間
    • atime:文件上一次打開的時間
  • 硬連接數
  • 文件數據block的位置

總之,除了文件名之外的全部文件信息,都存在inode之中。至於爲何沒有文件名,下文會有詳細解釋

inode號碼

每一個inode都有一個號碼,操做系統用inode號碼來識別文件。對於系統來講,文件名只是inode號碼便於識別的別稱或綽號。

表面上,用戶經過文件名,打開文件。實際上,系統內部這個過程分爲三步:

  • 系統找到這個文件名對應的inode號碼
  • 經過inode號碼,獲取inode信息
  • 根據inode信息,找到文件數據所在的block,讀出數據
相關文章
相關標籤/搜索