常見C++面試題及基本知識點總結(一)

【轉載請註明出處】:http://www.cnblogs.com/LUO77/p/5771237.html html

1. 結構體和共同體的區別。mysql

定義:linux

結構體struct:把不一樣類型的數據組合成一個總體,自定義類型。ios

共同體union:使幾個不一樣類型的變量共同佔用一段內存。c++

地址:程序員

struct和union都有內存對齊,結構體的內存佈局依賴於CPU、操做系統、編譯器及編譯時的對齊選項。面試

關於內存對齊,先讓咱們看四個重要的基本概念:
1.數據類型自身的對齊值:
對於char型數據,其自身對齊值爲1,對於short型爲2,對於int,float,double類型,其自身對齊值爲4,單位字節。
2.結構體或者類的自身對齊值:其成員中自身對齊值最大的那個值。
3.指定對齊值:#pragma pack(n),n=1,2,4,8,16改變系統的對齊係數
4.數據成員、結構體和類的有效對齊值:自身對齊值和指定對齊值中小的那個值。

 常見數據類型及其長度:算法

注意long int和int同樣是4byte,long double和double同樣是8byte。(關於long double,ANSI C標準規定了double變量存儲爲 IEEE 64 位(8 個字節)浮點數值,但並未規定long double的確切精度。因此對於不一樣平臺可能有不一樣的實現。有的是8字節,有的是10字節,有的是12字節或16字節。)sql

在標準c++中,int的定義長度要依靠你的機器的字長,也就是說,若是你的機器是32位的,int的長度爲32位,若是你的機器是64位的,那麼int的標準長度就是64位。經測試,在64位操做系統下,int的長度仍是32位的。數據庫

從上面的一段文字中,咱們能夠看出,首先根據結構體內部成員的自身對齊值獲得結構體的自身對齊值(內部成員最大的長度),若是沒有修改系統設定的默認補齊長度4的話,取較小的進行內存補齊。

結構體struct:不一樣之處,stuct裏每一個成員都有本身獨立的地址。sizeof(struct)是內存對齊後全部成員長度的加和。

共同體union:當共同體中存入新的數據後,原有的成員就失去了做用,新的數據被寫到union的地址中。sizeof(union)是最長的數據成員的長度。

總結: struct和union都是由多個不一樣的數據類型成員組成, 但在任何同一時刻, union中只存放了一個被選中的成員, 而struct的全部成員都存在。在struct中,各成員都佔有本身的內存空間,它們是同時存在的。一個struct變量的總長度等於全部成員長度之和。在Union中,全部成員不能同時佔用它的內存空間,它們不能同時存在。Union變量的長度等於最長的成員的長度。對於union的不一樣成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對於struct的不一樣成員賦值是互不影響的。

 


 

 2.static 和const分別怎麼用,類裏面static和const能夠同時修飾成員函數嗎。

 static的做用:

對變量:

1.局部變量:

在局部變量以前加上關鍵字static,局部變量就被定義成爲一個局部靜態變量。

  1)內存中的位置:靜態存儲區

  2)初始化:局部的靜態變量只能被初始化一次,且C中不能夠用變量對其初始化,而C++能夠用變量對其初始化。(詳見:http://www.cnblogs.com/novice-dxx/p/7094690.html

  3)做用域:做用域仍爲局部做用域,當定義它的函數或者語句塊結束的時候,做用域隨之結束。

 注:當static用來修飾局部變量的時候,它就改變了局部變量的存儲位置(從原來的棧中存放改成靜態存儲區)及其生命週期(局部靜態變量在離開做用域以後,並無被銷燬,而是仍然駐留在內存當中,直到程序結束,只不過咱們不能再對他進行訪問),但未改變其做用域。

2.全局變量

在全局變量以前加上關鍵字static,全局變量就被定義成爲一個全局靜態變量。

1)內存中的位置:靜態存儲區(靜態存儲區在整個程序運行期間都存在)

2)初始化:未經初始化的全局靜態變量會被程序自動初始化爲0(自動對象的值是任意的,除非他被顯示初始化)

3)做用域:全局靜態變量在聲明他的文件以外是不可見的。準確地講從定義之處開始到文件結尾。

注:static修飾全局變量,並未改變其存儲位置及生命週期,而是改變了其做用域,使當前文件外的源文件沒法訪問該變量,好處以下:(1)不會被其餘文件所訪問,修改(2)其餘文件中可使用相同名字的變量,不會發生衝突。對全局函數也是有隱藏做用。而普通全局變量只要定義了,任何地方都能使用,使用前須要聲明全部的.c文件,只能定義一次普通全局變量,可是能夠聲明屢次(外部連接)。注意:全局變量的做用域是全局範圍,可是在某個文件中使用時,必須先聲明。

對類中的:

    1.成員變量

    用static修飾類的數據成員實際使其成爲類的全局變量,會被類的全部對象共享,包括派生類的對象。所以,static成員必須在類外進行初始化(初始化格式: int base::var=10;),而不能在構造函數內進行初始化,不過也能夠用const修飾static數據成員在類內初始化 。

    特色:

    1. 不要試圖在頭文件中定義(初始化)靜態數據成員。在大多數的狀況下,這樣作會引發重複定義這樣的錯誤。即便加上#ifndef #define #endif或者#pragma once也不行。 
    2. 靜態數據成員能夠成爲成員函數的可選參數,而普通數據成員則不能夠。
    3. 靜態數據成員的類型能夠是所屬類的類型,而普通數據成員則不能夠。普通數據成員的只能聲明爲 所屬類類型的指針或引用。

2.成員函數

    1. 用static修飾成員函數,使這個類只存在這一份函數,全部對象共享該函數,不含this指針。
    2. 靜態成員是能夠獨立訪問的,也就是說,無須建立任何對象實例就能夠訪問。base::func(5,3);當static成員函數在類外定義時不須要加static修飾符。
    3. 在靜態成員函數的實現中不能直接引用類中說明的非靜態成員,能夠引用類中說明的靜態成員。由於靜態成員函數不含this指針。 

不能夠同時用const和static修飾成員函數。

C++編譯器在實現const的成員函數的時候爲了確保該函數不能修改類的實例的狀態,會在函數中添加一個隱式的參數const this*。但當一個成員爲static的時候,該函數是沒有this指針的。也就是說此時const的用法和static是衝突的。

咱們也能夠這樣理解:二者的語意是矛盾的。static的做用是表示該函數只做用在類型的靜態變量上,與類的實例沒有關係;而const的做用是確保函數不能修改類的實例的狀態,與類型的靜態變量沒有關係。所以不能同時用它們。

const的做用:

 1.限定變量爲不可修改。

2.限定成員函數不能夠修改任何數據成員。

3.const與指針:

const char *p 表示 指向的內容不能改變。

char * const p,就是將P聲明爲常指針,它的地址不能改變,是固定的,可是它的內容能夠改變。


 

 3.指針和引用的區別,引用能夠用常指針實現嗎。

本質上的區別是,指針是一個新的變量,只是這個變量存儲的是另外一個變量的地址,咱們經過訪問這個地址來修改變量。

而引用只是一個別名,仍是變量自己。對引用進行的任何操做就是對變量自己進行操做,所以以達到修改變量的目的。

(1)指針:指針是一個變量,只不過這個變量存儲的是一個地址,指向內存的一個存儲單元;而引用跟原來的變量實質上是同一個東西,只不過是原變量的一個別名而已。如:
int a=1;int *p=&a;
int a=1;int &b=a;
上面定義了一個整形變量和一個指針變量p,該指針變量指向a的存儲單元,即p的值是a存儲單元的地址。
而下面2句定義了一個整形變量a和這個整形a的引用b,事實上a和b是同一個東西,在內存佔有同一個存儲單元。
(2)能夠有const指針,可是沒有const引用(const引用可讀不可改,與綁定對象是否爲const無關)
(3)指針能夠有多級,可是引用只能是一級(int **p;合法 而 int &&a是不合法的)
(4)指針的值能夠爲,可是引用的值不能爲NULL,而且引用在定義的時候必須初始化;
(5)指針的值在初始化後能夠改變,即指向其它的存儲單元,而引用在進行初始化後就不會再改變了。
(6)"sizeof引用"獲得的是所指向的變量(對象)的大小,而"sizeof指針"獲得的是指針自己的大小;
(7)指針和引用的自增(++)運算意義不同;
指針傳參的時候,仍是值傳遞,試圖修改傳進來的指針的值是不能夠的。只能修改地址所保存變量的值。
引用傳參的時候,傳進來的就是變量自己,所以能夠被修改。

4.什麼是多態,多態有什麼用途。

  1. 定義:「一個接口,多種方法」,程序在運行時才決定調用的函數。
  2. 實現:C++多態性主要是經過虛函數實現的,虛函數容許子類重寫override(注意和overload的區別,overload是重載,是容許同名函數的表現,這些函數參數列表/類型不一樣)。
多態與非多態的實質區別就是函數地址是早綁定仍是晚綁定。若是函數的調用,在編譯器編譯期間就能夠肯定函數的調用地址,並生產代碼,是靜態的,就是說地址是早綁定的。而若是函數調用的地址不能在編譯器期間肯定,須要在運行時才肯定,這就屬於晚綁定。

3.目的:接口重用。封裝可使得代碼模塊化,繼承能夠擴展已存在的代碼,他們的目的都是爲了代碼重用。而多態的目的則是爲了接口重用。

4.用法:聲明基類的指針,利用該指針指向任意一個子類對象,調用相應的虛函數,能夠根據指向的子類的不一樣而實現不一樣的方法。

補充一下關於重載、重寫、隱藏(老是不記得)的區別:

Overload(重載):在C++程序中,能夠將語義、功能類似的幾個函數用同一個名字表示,但參數或返回值不一樣(包括類型、順序不一樣),即函數重載。
(1)相同的範圍(在同一個類中);
(2)函數名字相同;
(3)參數不一樣;
(4)virtual 關鍵字無關緊要。
Override(重寫):是指派生類函數覆蓋基類函數,特徵是:
(1)不一樣的範圍(分別位於派生類與基類);
(2)函數名字相同;
(3)參數相同;
(4)基類函數必須有virtual 關鍵字。
注:重寫基類虛函數的時候,會自動轉換這個函數爲virtual函數,無論有沒有加virtual,所以重寫的時候不加virtual也是能夠的,不過爲了易讀性,仍是加上比較好。 Overwrite(隱藏):隱藏,是指派生類的函數屏蔽了與其同名的基類函數,規則以下: (1)若是派生類的函數與基類的函數同名,可是參數不一樣。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。 (2)若是派生類的函數與基類的函數同名,而且參數也相同,可是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。

補充一下虛函數表:

多態是由虛函數實現的,而虛函數主要是經過虛函數表(V-Table)來實現的。

若是一個類中包含虛函數(virtual修飾的函數),那麼這個類就會包含一張虛函數表,虛函數表存儲的每一項是一個虛函數的地址。以下圖:

這個類的每個對象都會包含一個虛指針(虛指針存在於對象實例地址的最前面,保證虛函數表有最高的性能),這個虛指針指向虛函數表。

注:對象不包含虛函數表,只有虛指針,類才包含虛函數表,派生類會生成一個兼容基類的虛函數表。

  • 原始基類的虛函數表

  下圖是原始基類的對象,能夠看到虛指針在地址的最前面,指向基類的虛函數表(假設基類定義了3個虛函數)

  • 單繼承時的虛函數(無重寫基類虛函數

假設如今派生類繼承基類,而且從新定義了3個虛函數,派生類會本身產生一個兼容基類虛函數表的屬於本身的虛函數表

  Derive class 繼承了 Base class 中的三個虛函數,準確的說,是該函數實體的地址被拷貝到 Derive類的虛函數表,派生類新增的虛函數置於虛函數表的後面,並按聲明順序存放

  • 單繼承時的虛函數(重寫基類虛函數

如今派生類重寫基類的x函數,能夠看到這個派生類構建本身的虛函數表的時候,修改了base::x()這一項,指向了本身的虛函數。

  • 多重繼承時的虛函數(Derived ::public Base1,public Base2)

這個派生類多重繼承了兩個基類base1,base2,所以它有兩個虛函數表。

  

  它的對象會有多個虛指針(聽說和編譯器相關),指向不一樣的虛函數表。

  多重繼承時指針的調整:

Derive b;
Base1* ptr1 = &b;   // 指向 b 的初始地址
Base2* ptr2 = &b;   // 指向 b 的第二個子對象

由於 Base1 是第一個基類,因此 ptr1 指向的是 Derive 對象的起始地址,不須要調整指針(偏移)。

由於 Base2 是第二個基類,因此必須對指針進行調整,即加上一個 offset,讓 ptr2 指向 Base2 子對象。

固然,上述過程是由編譯器完成的。

Base1* b1 = (Base1*)ptr2;
b1->y();                   // 輸出 Base2::y()
Base2* b2 = (Base2*)ptr1;
b2->y();                   // 輸出 Base1::y()

其實,經過某個類型的指針訪問某個成員時,編譯器只是根據類型的定義查找這個成員所在偏移量,用這個偏移量獲取成員。因爲 ptr2 原本就指向 Base2 子對象的起始地址,因此b1->y()調用到的是Base2::y(),而 ptr1 原本就指向 Base1 子對象的起始地址(即 Derive對象的起始地址),因此b2->y()調用到的是Base1::y()

  • 虛繼承時的虛函數表

  虛繼承的引入把對象的模型變得十分複雜,除了每一個基類(MyClassA和MyClassB)和公共基類(MyClass)的虛函數表指針須要記錄外,每一個虛擬繼承了MyClass的父類還須要記錄一個虛基類表vbtable的指針vbptr。MyClassC的對象模型如圖4所示。

  

   虛基類表每項記錄了被繼承的虛基類子對象相對於虛基類表指針的偏移量。好比MyClassA的虛基類表第二項記錄值爲24,正是MyClass::vfptr相對於MyClassA::vbptr的偏移量,同理MyClassB的虛基類表第二項記錄值12也正是MyClass::vfptr相對於MyClassA::vbptr的偏移量。(虛函數與虛繼承深刻探討

對象模型探討:

 

1.沒有繼承狀況,vptr存放在對象的開始位置,如下是Base1的內存佈局

m_iData :100


 2.單繼承的狀況下,對象只有一個vptr,它存放在對象的開始位置,派生類子對象在父類子對象的最後面,如下是D1的內存佈局

B1:: m_iData : 100

B1::vptr : 4294800

B2::vptr : 4294776

D::m_iData :300


4. 虛擬繼承狀況下,虛父類子對象會放在派生類子對象以後,派生類子對象的第一個位置存放着一個vptr,虛擬子類子對象也會保存一個vptr,如下是VD1的內存佈局

 

 Unknown : 4294888

B1::vptr :4294864

VD1::vptr :        4294944

VD1::m_iData :  200

VD2::Unknown : 4294952

VD::m_iData : 500

B1::m_iData :  100

5. 棱形繼承的狀況下,非虛基類子對象在派生類子對象前面,並按照聲明順序排列,虛基類子對象在派生類子對象後面

VD1::Unknown : 4294968

VD2::vptr :    4   294932

VD2::m_iData : 300

B1::vptr :       4294920

B1::m_iData :  100

 

補充一下純虛函數:

  • 定義: 在不少狀況下,基類自己生成對象是不合情理的。爲了解決這個問題,方便使用類的多態性,引入了純虛函數的概念,將函數定義爲純虛函數(方法:virtual ReturnType Function()= 0;)純虛函數不能再在基類中實現,編譯器要求在派生類中必須予以重寫以實現多態性。同時含有純虛擬函數的類稱爲抽象類,它不能生成對象。
  • 特色:

1,當想在基類中抽象出一個方法,且該基類只作能被繼承,而不能被實例化;(避免類被實例化且在編譯時候被發現,能夠採用此方法)

2,這個方法必須在派生類(derived class)中被實現;

  • 目的:使派生類僅僅只是繼承函數的接口。
補充一下純虛函數:
  • 定義:稱帶有純虛函數的類爲抽象類。
  • 做用:爲一個繼承體系提供一個公共的根,爲派生類提供操做接口的通用語義。
  • 特色:1.抽象類只能做爲基類來使用,而繼承了抽象類的派生類若是沒有實現純虛函數,而只是繼承純虛函數,那麼該類仍舊是一個抽象類,若是實現了純虛函數,就再也不是抽象類。
      2.抽象類不能夠定義對象。
補充一下多重繼承和虛繼承:
多重繼承:
定義:派生類繼承多個基類,派生類爲每一個基類(顯式或隱式地)指定了訪問級別—— publicprotected 或  private
    class Panda : public Bear, public Endangered {
    }

構造:

    1. 派生類的對象包含每一個基類的基類子對象。
    2. 派生類構造函數初始化全部基類(多重繼承中若沒有顯式調用某個基類的構造函數,則編譯器會調用該基類默認構造函數),派生類只能初始化本身的基類,並不須要考慮基類的基類怎麼初始化。
    3. 多重繼承時,基類構造函數按照基類構造函數在類派生列表中的出現次序調用。
析構:老是按構造函數運行的 逆序調用析構函數。(基類的析構函數最好寫成virtual,不然再子類對象銷燬的時候,沒法銷燬子類對象部分資源。) 假定全部根基類都將它們的析構函數適當定義爲虛函數,那麼,不管經過哪一種指針類型刪除對象,虛析構函數的處理都是一致的。
 
拷貝構造/賦值:若是要爲派生類編寫拷貝構造函數,則須要爲調用基類相應拷貝構造函數併爲其傳遞參數,不然只會拷貝派生類部分。
深拷貝與淺拷貝:
淺拷貝:默認的複製構造函數只是完成了對象之間的位拷貝,也就是把對象裏的值徹底複製給另外一個對象,如A=B。這時,若是B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。
    這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。 深拷貝:自定義複製構造函數須要注意,對象之間發生複製,資源從新分配,即A有5個空間,B也應該有5個空間,而不是指向A的5個空間。

虛繼承與虛基類:

定義:在多重繼承下,一個基類能夠在派生層次中出現屢次。(派生類對象中可能出現多個基類對象)在 C++ 中,經過使用虛繼承解決這類問題。虛繼承是一種機制,類經過虛繼承指出它但願共享其虛基類的狀態。在虛繼承下,對給定虛基類,不管該類在派生層次中做爲虛基類出現多少次,只繼承一個共享的基類子對象。共享的基類子對象稱爲虛基類

用法: istream 和  ostream 類對它們的基類進行虛繼承。經過使基類成爲虛基類, istream 和  ostream 指定,若是其餘類(如  iostream 同時繼承它們兩個,則派生類中只出現它們的公共基類ios的一個副本。經過在派生列表中包含關鍵字  virtual 設置虛基類:
    class istream : public virtual ios { ... };
    class ostream : virtual public ios { ... };
    // iostream inherits only one copy of its ios base class
    class iostream: public istream, public ostream { ... };

5.各個排序算法的時間複雜度和穩定性,快排的原理。

排序深刻探討

  • 插入排序

  每次將一個待排序的數據,跟前面已經有序的序列的數字一一比較找到本身合適的位置,插入到序列中,直到所有數據插入完成。

  • 希爾排序

  先將整個待排元素序列分割成若干個子序列(由相隔某個「增量」的元素組成的)分別進行直接插入排序,而後依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。因爲希爾排序是對相隔若干距離的數據進行直接插入排序,所以能夠形象的稱希爾排序爲「跳着插

  • 冒泡排序

經過交換使相鄰的兩個數變成小數在前大數在後,這樣每次遍歷後,最大的數就「沉」到最後面了。重複N次便可以使數組有序。

冒泡排序改進1:在某次遍歷中若是沒有數據交換,說明整個數組已經有序。所以經過設置標誌位來記錄這次遍歷有無數據交換就能夠判斷是否要繼續循環。

冒泡排序改進2:記錄某次遍歷時最後發生數據交換的位置,這個位置以後的數據顯然已經有序了。所以經過記錄最後發生數據交換的位置就能夠肯定下次循環的範圍了。

  • 快速排序

「挖坑填數+分治法」,首先令i =L; j = R; 將a[i]挖出造成第一個坑,稱a[i]爲基準數。而後j--由後向前找比基準數小的數,找到後挖出此數填入前一個坑a[i]中,再i++由前向後找比基準數大的數,找到後也挖出此數填到前一個坑a[j]中。重複進行這種「挖坑填數」直到i==j。再將基準數填入a[i]中,這樣i以前的數都比基準數小,i以後的數都比基準數大。所以將數組分紅二部分再分別重複上述步驟就完成了排序。

  • 選擇排序

數組分紅有序區和無序區,初始時整個數組都是無序區,而後每次從無序區選一個最小的元素直接放到有序區的最後,直到整個數組變有序區。

  • 堆排序

堆的插入就是——每次插入都是將新數據放在數組最後,而從這個新數據的父結點到根結點一定是一個有序的數列,所以只要將這個新數據插入到這個有序數列中便可。

堆的刪除就是——堆的刪除就是將最後一個數據的值賦給根結點,而後再從根結點開始進行一次從上向下的調整。調整時先在左右兒子結點中找最小的,若是父結點比這個最小的子結點還小說明不須要調整了,反之將父結點和它交換後再考慮後面的結點。至關於從根結點開始將一個數據在有序數列中進行「下沉」。

所以,堆的插入和刪除很是相似直接插入排序,只不是在二叉樹上進行插入過程。因此能夠將堆排序形容爲「樹上插

  • 歸併排序

歸併排序主要分爲兩步:分數列(divide),每次把數列一分爲二,而後分到只有兩個元素的小數列;合數列(Merge),合併兩個已經內部有序的子序列,直至全部數字有序。用遞歸能夠實現。

  • 基數排序(桶排序)

基數排序,第一步根據數字的個位分配到每一個桶裏,在桶內部排序,而後將數字再輸出(串起來);而後根據十位分桶,繼續排序,再串起來。直至全部位被比較完,全部數字已經有序。

   


 

6.vector中size()和capacity()的區別。

 

size()指容器當前擁有的元素個數(對應的resize(size_type)會在容器尾添加或刪除一些元素,來調整容器中實際的內容,使容器達到指定的大小。);capacity()指容器在必須分配存儲空間以前能夠存儲的元素總數。

 

size表示的這個vector裏容納了多少個元素,capacity表示vector可以容納多少元素,它們的不一樣是在於vector的size是2倍增加的。若是vector的大小不夠了,好比如今的capacity是4,插入到第五個元素的時候,發現不夠了,此時會給他從新分配8個空間,把原來的數據及新的數據複製到這個新分配的空間裏。(會有迭代器失效的問題)

各容器的特色:

 


7.map和set的原理。

(map和set的四個問題)

map和set的底層實現主要是由紅黑樹實現的。

紅黑樹:

性質1 節點是 紅色黑色
性質2 根節點是 黑色
性質3 每一個葉節點(NIL節點,空節點)是 黑色的。
性質4 每一個 紅色節點的兩個子節點都是黑色。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點)
性質5 從任一節點到其每一個葉子的全部路徑都包含相同數目的 黑色節點。
這些約束的好處是:保持了樹的相對平衡,同時又比AVL的插入刪除操做的複雜性要低許多。

深刻探討紅黑樹


 

8.tcp爲何要三次握手,tcp爲何可靠。

爲何不能兩次握手:(防止已失效的鏈接請求又傳送到服務器端,於是產生錯誤)

假設改成兩次握手,client端發送的一個鏈接請求在服務器滯留了,這個鏈接請求是無效的,client已是closed的狀態了,而服務器認爲client想要創建

一個新的鏈接,因而向client發送確認報文段,而client端是closed狀態,不管收到什麼報文都會丟棄。而若是是兩次握手的話,此時就已經創建鏈接了。

服務器此時會一直等到client端發來數據,這樣就浪費掉不少server端的資源。

(校注:此時由於client沒有發起創建鏈接請求,因此client處於CLOSED狀態,接受到任何包都會丟棄,謝希仁舉的例子就是這種場景。可是若是服務器發送對這個延誤的舊鏈接報文的確認的同時,客戶端調用connect函數發起了鏈接,就會使客戶端進入SYN_SEND狀態,當服務器那個對延誤舊鏈接報文的確認傳到客戶端時,由於客戶端已經處於SYN_SEND狀態,因此就會使客戶端進入ESTABLISHED狀態,此時服務器端反而丟棄了這個重複的經過connect函數發送的SYN包,見第三個圖。而鏈接創建以後,發送包因爲SEQ是以被丟棄的SYN包的序號爲準,而服務器接收序號是以那個延誤舊鏈接SYN報文序號爲準,致使服務器丟棄後續發送的數據包)

三次握手的最主要目的是保證鏈接是雙工的,可靠更多的是經過重傳機制來保證的。 

TCP可靠傳輸的實現:

TCP 鏈接的每一端都必須設有兩個窗口——一個發送窗口和一個接收窗口。TCP 的可靠傳輸機制用字節的序號進行控制。TCP 全部的確認都是基於序號而不是基於報文段。
發送過的數據未收到確認以前必須保留,以便超時重傳時使用。發送窗口沒收到確認不動,和收到新的確認後前移。

發送緩存用來暫時存放: 發送應用程序傳送給發送方 TCP 準備發送的數據;TCP 已發送出但還沒有收到確認的數據。

接收緩存用來暫時存放:按序到達的、但還沒有被接收應用程序讀取的數據; 不按序到達的數據。

必須強調三點:
    1>   A 的發送窗口並不老是和 B 的接收窗口同樣大(由於有必定的時間滯後)。
    2>   TCP 標準沒有規定對不按序到達的數據應如何處理。一般是先臨時存放在接收窗口中,等到字節流中所缺乏的字節收到後,再按序交付上層的應用進程。
    3>   TCP 要求接收方必須有累積確認的功能,這樣能夠減少傳輸開銷(累積確認:通常地講,若是發送方發了包1,包2,包3,包4;接受方成功收到包1,包2,包3。
那麼接受方能夠發回一個確認包,序號爲4(4表示指望下一個收到的包的序號;固然你約定好用3表示也能夠),那麼發送方就知道包1到包3都發送接收成功,必要時重發包4。一個確認包確認了累積到某一序號的全部包。而不是對沒個序號都發確認包。)
  • TCP報文格式

  (1)序號:Seq序號,佔32位,用來標識從TCP源端向目的端發送的字節流,發起方發送數據時對此進行標記。
  (2)確認序號:Ack序號,佔32位,只有ACK標誌位爲1時,確認序號字段纔有效,Ack=Seq+1。
  (3)標誌位:共6個,即URG、ACK、PSH、RST、SYN、FIN等,具體含義以下:
    (A)URG:緊急指針(urgent pointer)有效。
    (B)ACK:確認序號有效。
    (C)PSH:接收方應該儘快將這個報文交給應用層。
    (D)RST:重置鏈接。
    (E)SYN:發起一個新鏈接。
    (F)FIN:釋放一個鏈接。

 須要注意的是:
  (A)不要將確認序號Ack與標誌位中的ACK搞混了。
  (B)確認方Ack=發起方Req+1,兩端配對。

  • 三次握手

TCP三次即創建TCP鏈接,指創建一個TCP鏈接時,須要客戶端服務端總共發送3 個包以確認鏈接的創建。在socket編程中,這一過程當中由客戶端執行connect來觸發,流程以下:

(1)第一次握手:Client將標誌位SYN置爲1(表示要發起一個鏈接),隨機產生一個值seq=J,並將該數據包發送給Server,Client進入SYN_SENT狀態,等待Server確認。
(2)第二次握手:Server收到數據包後由標誌位SYN=1知道Client請求創建鏈接,Server將標誌位SYN和ACK都置爲1,ack=J+1,隨機產生一個值seq=K,並將該數據包發送給Client以確認鏈接請求,Server進入SYN_RCVD狀態。
(3)第三次握手:Client收到確認後,檢查ack是否爲J+1,ACK是否爲1,若是正確則將標誌位ACK置爲1,ack=K+1,並將該數據包發送給Server,Server檢查ack是否爲K+1,ACK是否爲1,若是正確則鏈接創建成功,Client和Server進入ESTABLISHED狀態,完成三次握手,隨後Client與Server之間能夠開始傳輸數據了。

 

SYN攻擊:
  在三次握手過程當中,Server發送SYN-ACK以後,收到Client的ACK以前的TCP鏈接稱爲半鏈接(half-open connect),此時Server處於SYN_RCVD狀態,當收到ACK後,Server轉入ESTABLISHED狀態。SYN攻擊就是Client在短期內僞造大量不存在的IP地址,並向Server不斷地發送SYN包,Server回覆確認包,並等待Client的確認,因爲源地址是不存在的,所以,Server須要不斷重發直至超時,這些僞造的SYN包將產時間佔用未鏈接隊列,致使正常的SYN請求由於隊列滿而被丟棄,從而引發網絡堵塞甚至系統癱瘓。SYN攻擊時一種典型的DDOS攻擊,檢測SYN攻擊的方式很是簡單,即當Server上有大量半鏈接狀態且源IP地址是隨機的,則能夠判定遭到SYN攻擊了,使用以下命令可讓之現行:
  #netstat -nap | grep SYN_RECV
ddos攻擊:
分佈式拒絕服務(DDoS:Distributed Denial of Service)攻擊指藉助於客戶/服務器技術,將多個計算機聯合起來做爲攻擊平臺,對一個或多個目標發動DDoS攻擊,從而成倍地提升拒絕服務攻擊的威力。一般,攻擊者使用一個偷竊賬號將DDoS主控程序安裝在一個計算機上,在一個設定的時間主控程序將與大量代理程序通信,代理程序已經被安裝在網絡上的許多計算機上。代理程序收到指令時就發動攻擊。利用客戶/服務器技術,主控程序能在幾秒鐘內激活成百上千次代理程序的運行。

 

  • 四次揮手

所謂四次揮手(Four-Way Wavehand)即終止TCP鏈接,就是指斷開一個TCP鏈接時,須要客戶端和服務端總共發送4個包以確認鏈接的斷開。在socket編程中,這一過程由客戶端或服務端任一方執行close來觸發,整個流程以下圖所示:

因爲TCP鏈接時全雙工的,所以,每一個方向都必需要單獨進行關閉,這一原則是當一方完成數據發送任務後,發送一個FIN來終止這一方向的鏈接,收到一個FIN只是意味着這一方向上沒有數據流動了,即不會再收到數據了,可是在這個TCP鏈接上仍然可以發送數據,直到這一方向也發送了FIN。首先進行關閉的一方將執行主動關閉,而另外一方則執行被動關閉,上圖描述的便是如此。
 (1)第一次揮手:Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入FIN_WAIT_1狀態。
 (2)第二次揮手:Server收到FIN後,發送一個ACK給Client,確認序號爲收到序號+1(與SYN相同,一個FIN佔用一個序號),Server進入CLOSE_WAIT狀態。
 (3)第三次揮手:Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK狀態。
 (4)第四次揮手:Client收到FIN後,Client進入TIME_WAIT狀態,接着發送一個ACK給Server,確認序號爲收到序號+1,Server進入CLOSED狀態,完成四次揮手。

 爲何須要TIME_WAIT

TIMEWAIT狀態也稱爲2MSL等待狀態

 1)爲實現TCP這種全雙工(full-duplex)鏈接的可靠釋放

這樣可以讓TCP再次發送最後的ACK以防這個ACK丟失(另外一端超時並重發最後的FIN)。這種2MSL等待的另外一個結果是這個TCP鏈接在2MSL等待期間,定義這個鏈接的插口(客戶的IP地址和端口號,服務器的IP地址和端口號)不能再被使用。這個鏈接只能在2MSL結束後才能再被使用。

2)爲使舊的數據包在網絡因過時而消失

每一個具體TCP實現必須選擇一個報文段最大生存時間MSL(Maximum Segment Lifetime)。它是任何報文段被丟棄前在網絡內的最長時間。

爲何創建鏈接是三次握手,而關閉鏈接倒是四次揮手呢?

 這是由於服務端在LISTEN狀態下,收到創建鏈接請求的SYN報文後,把ACK和SYN放在一個報文裏發送給客戶端。而關閉鏈接時,當收到對方的FIN報文時,僅僅表示對方再也不發送數據了可是還能接收數據,咱們也未必所有數據都發送給對方了,因此咱們不能夠當即close,也能夠發送一些數據給對方後,再發送FIN報文給對方來表示贊成如今關閉鏈接,所以,咱們的ACK和FIN通常都會分開發送。


 9.函數調用和系統調用的區別。

什麼是系統調用?(常見Linux及其分類表

所謂系統調用就是用戶在程序中調用操做系統所提供的一個子功能,也就是系統API,系統調用能夠被看作特殊的公共子程序。系統中的各類共享資源都由操做系通通一掌管,所以在用戶程序中,凡是與資源有關的操做(如存儲分配、進行I/O傳輸及管理文件等),都必須經過系統調用方式向操做系統提出服務請求,並由操做系統代爲完成。一般,一個操做系統提供的系統調用命令有幾十個乃至上百個之多。這些系統調用按照功能大體能夠分爲如下幾類:

  • 設備管理:完成設備的請求或釋放,以及設備啓動等功能。
  • 文件管理:完成文件的讀、寫、建立及刪除等功能
  • 進程控制:完成進程的建立、撤銷、阻塞、及喚醒的功能
  • 進程通訊:完成進程之間的消息傳遞或信號的傳遞
  • 內存管理:完成內存的分配、回收以及獲取做業佔用內存區大小及始址等功能。

顯然,系統調用運行在系統的核心態。經過系統調用的方式來使用系統功能,能夠保證系統的穩定性和安全性,防止用戶隨意更改或訪問系統的數據或命令。系統調用命令式由操做系統提供的一個或多個子程序模塊來實現的。

下圖詳細闡述了,Linux系統中系統調用的過程:(int 0x80中斷向量是dos系統返回,int 3中斷向量是斷點指令——能夠查中斷向量表)

 

庫是可重用的模塊,處於用戶態。
系統調用是操做系統提供的服務,處於內核態,不能直接調用,而要使用相似int 0x80的軟中斷陷入內核,因此庫函數中有很大部分是對系統調用的封裝。

既然如此,如何調用系統調用?

用戶是處於用戶態,具備的權限是很是有限,確定是不能直接使用內核態的服務,只能間接經過有訪問權限的API函數內嵌的系統調用函數來調用。

介紹下系統調用的過程:
首先將API函數參數壓到上,而後將函數內調用系統調用的代碼放入寄存器經過陷入中斷進入內核將控制權交給操做系統操做系統得到控制後將系統調用代碼拿出來跟操做系統一直維護的一張系統調用表作比較已找到該系統調用程序體的內存地址接着訪問該地址執行系統調用。執行完畢後,返回用戶程序

例子:

int main()
{
    int fd = create("filename",0666);
    exit(0);
}
在執行main函數時,是在user mode下執行,當遇到create函數時,繼續在user mode下執行,而後將filename和0666兩個參數壓入棧中寄存器,接着調用庫函數create,系統仍然處於user mode。這裏的庫函數create實際上 調用了內核的系統調用create,執行到這裏後,系統將create系統調用的unique number壓入寄存器,而後 執行指令trap使系統進入kernel mode(執行int $0x80產生中斷)。這時系統意識到要進行系統調用的invoke,因而從剛纔的寄存器中取出create系統調用的unique number,從系統調用表中得知要invoke的系統調用是create,而後執行。執行完畢返回庫函數create的調用,庫函數負責檢查系統調用的執行狀況(檢查某些寄存器的值),而後庫函數create根據檢查的結果返回響應的值。
 
這裏 trap指令相似於一個系統中斷而且是軟中斷,而系統調用create相似於一箇中斷處理函數全部的系統調用都與上邊的狀況相似,靠 中斷機制切換到內核模式實現
系統調用一般比庫函數要慢,由於要把上下文環境切換到內核模式。

 補充一下系統調用和庫函數的區別:

系統調用:是操做系統爲用戶態運行的進程和硬件設備(如CPU、磁盤、打印機等)進行交互提供的一組接口,即就是設置在應用程序和硬件設備之間的一個接口層。能夠說是操做系統留給用戶程序的一個接口。再來講一下,linux內核是單內核,結構緊湊,執行速度快,各個模塊之間是直接調用的關係。放眼望整個linux系統,從上到下依次是用戶進程->linux內核->硬件。其中系統調用接口是位於Linux內核中的,若是再稍微細分一下的話,整個linux系統從上到下能夠是:用戶進程->系統調用接口->linux內核子系統->硬件,也就是說Linux內核包括了系統調用接口和內核子系統兩部分;或者從下到上能夠是:物理硬件->OS內核->OS服務->應用程序,其中操做系統起到「承上啓下」的關鍵做用,向下管理物理硬件,向上爲操做系服務和應用程序提供接口,這裏的接口就是系統調用了。
       通常地,操做系統爲了考慮實現的難度和管理的方便,它只提供一少部分的系統調用,這些系統調用通常都是由C和彙編混合編寫實現的,其接口用C來定義,而具體的實現則是彙編,這樣的好處就是執行效率高,並且,極大的方便了上層調用。

庫函數:顧名思義是把函數放到庫裏。是把一些經常使用到的函數編完放到一個文件裏,供別人用。別人用的時候把它所在的文件名用#include<>加到裏面就能夠了。通常是放到lib文件裏的。通常是指編譯器提供的可在c源程序中調用的函數。可分爲兩類,一類是c語言標準規定的庫函數,一類是編譯器特定的庫函數。(因爲版權緣由,庫函數的源代碼通常是不可見的,但在頭文件中你能夠看到它對外的接口)
      libc中就是一個C標準庫,裏面存放一些基本函數,這些基本函數都是被標準化了的,並且這些函數一般都是用匯編直接實現的。
       庫函數通常能夠歸納的分爲兩類,一類是隨着操做系統提供的,另外一類是由第三方提供的。隨着系統提供的這些庫函數把系統調用進行封裝或者組合,能夠實現更多的功能,這樣的庫函數可以實現一些對內核來講比較複雜的操做。好比,read()函數根據參數,直接就能讀文件,而背後隱藏的好比文件在硬盤的哪一個磁道,哪一個扇區,加載到內存的哪一個位置等等這些操做,程序員是沒必要關心的,這些操做裏面天然也包含了系統調用。而對於第三方的庫,它其實和系統庫同樣,只是它直接利用系統調用的可能性要小一些,而是利用系統提供的API接口來實現功能(API的接口是開放的)。部分Libc庫中的函數的功能的實現仍是藉助了系統掉調用,好比printf的實現最終仍是調用了write這樣的系統調用;而另外一些則不會使用系統調用,好比strlen, strcat, memcpy等。

實時上,系統調用所提供給用戶的是直接而純粹的高級服務,若是想要更人性化,具備更符合特定狀況的功能,那麼就要咱們用戶本身來定義,所以就衍生了庫函數,它把部分系統調用包裝起來,一方面把系統調用抽象了,一方面方便了用戶級的調用。系統調用和庫函數在執行的效果上很類似(固然庫函數會更符合需求),可是系統調用是運行於內核狀態;而庫函數由用戶調用,運行於用戶態。

系統調用是爲了方便使用操做系統的接口,而庫函數則是爲了人們編程的方便。

 


 10.線程和進程,線程能夠共享進程裏的哪些東西。 知道協程是什麼嗎

進程,是併發執行的程序在執行過程當中分配和管理資源的基本單位,每個進程都有一個本身的地址空間,即進程空間或(虛空間)。進程空間的大小 只與處理機的位數有關,一個 16 位長處理機的進程空間大小爲 216 ,而 32 位處理機的進程空間大小爲 232 。進程至少有 5 種基本狀態,它們是:初始態,執行態,等待狀態,就緒狀態,終止狀態。

線程,在網絡或多用戶環境下,一個服務器一般須要接收大量且不肯定數量用戶的併發請求,爲每個請求都建立一個進程顯然是行不通的,——不管是從系統資源開銷方面或是響應用戶請求的效率方面來看。所以,操做系統中線程的概念便被引進了。線程,是進程的一部分,一個沒有線程的進程能夠被看做是單線程的。線程有時又被稱爲輕權進程或輕量級進程,也是 CPU 調度的一個基本單位。

共享進程的地址空間,全局變量(數據和堆)。在一個進程中,各個線程共享堆區,而進程中的線程各自維持本身的棧。

Each thread has its own:

  • 棧區和棧指針(Stack area and stack pointer)
  • 寄存器(Registers)
  • 調度優先級Scheduling properties (such as policy or priority)
  • 信號(阻塞和懸掛)Signals (pending and blocked signals)
  • 普通變量Thread specific data ( automatic variables )
線程是指進程內的一個執行單元,也是進程內的可調度實體.
與進程的區別:
(1)地址空間:進程內的一個執行單元;進程至少有一個線程;它們共享進程的地址空間;而進程有本身獨立的地址空間;
(2)資源擁有:進程是資源分配和擁有的單位,同一個進程內的線程共享進程的資源
(3)線程是處理器調度的基本單位,但進程不是.
4)兩者都可併發執行.

進程和線程都是由操做系統所體會的程序運行的基本單元,系統利用該基本單元實現系統對應用的併發性。進程和線程的區別在於:

簡而言之,一個程序至少有一個進程,一個進程至少有一個線程. 
線程的劃分尺度小於進程,使得多線程程序的併發性高。 
另外,進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存,從而極大地提升了程序的運行效率。 
線程在執行過程當中與進程仍是有區別的。每一個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。可是線程不可以獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。 
從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分能夠同時執行。但操做系統並無將多個線程看作多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。

進程是具備必定獨立功能的程序關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位. 
線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程本身基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),可是它可與同屬一個進程的其餘的線程共享進程所擁有的所有資源. 
一個線程能夠建立和撤銷另外一個線程;同一個進程中的多個線程之間能夠併發執行.

協程:

定義:協程其實能夠認爲是比線程更小的執行單元。爲啥說他是一個執行單元,由於他自帶CPU上下文。

協程切換:協程擁有本身的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。

     (咱們在本身在進程裏面完成邏輯流調度,碰着i\o我就用非阻塞式的。那麼咱們便可以利用到異步優點,又能夠避免反覆系統調用,還有進程切換形成的開銷,分分鐘給你上幾千個    邏輯流不費力。這就是協程。) 

協程的調度徹底由用戶控制,一個線程能夠有多個協程,用戶建立了幾個線程,而後每一個線程都是循環按照指定的任務清單順序完成不一樣的任務,當任務被堵塞的時候執行下一個任務,當恢復的時候再回來執行這個任務,任務之間的切換隻須要保存每一個任務的上下文內容,就像直接操做棧同樣的,這樣就徹底沒有內核切換的開銷,能夠不加鎖的訪問全局變量,因此上下文的切換很是快;另外協程還須要保證是非堵塞的且沒有相互依賴,協程基本上不能同步通信,多采用一步的消息通信,效率比較高。

多線程和多進程的優劣:

 

多線程仍是多進程的爭執由來已久,這種爭執最多見到在B/S通信中服務端併發技術的選型上,好比WEB服務器技術中,Apache是採用多進程的(perfork模式,每客戶鏈接對應一個進程,每進程中只存在惟一一個執行線程),Java的Web容器Tomcat、Websphere等都是多線程的(每客戶鏈接對應一個線程,全部線程都在一個進程中)。

 

 

 

多進程:fork

多線程:pthread_create

 


 11.mysql的數據庫引擎有哪些,他們的區別

ISAM

  ISAM是一個定義明確且歷經時間考驗的數據表格管理方法,它在設計之時就考慮到數據庫被查詢的次數要遠大於更新的次數。所以,ISAM執行讀取操做的速度很快,並且不佔用大量的內存和存儲資源。ISAM的兩個主要不足之處在於,它不支持事務處理,也不可以容錯:若是你的硬盤崩潰了,那麼數據文件就沒法恢復了。若是你正在把ISAM用在關鍵任務應用程序裏,那就必須常常備份你全部的實時數據,經過其複製特性,MYSQL可以支持這樣的備份應用程序。

MYISAM

  MYISAM是MYSQL的ISAM擴展格式和缺省的數據庫引擎。除了提供ISAM裏所沒有的索引和字段管理的大量功能,MYISAM還使用一種表格鎖定的機制,來優化多個併發的讀寫操做。其代價是你須要常常運行OPTIMIZE TABLE命令,來恢復被更新機制所浪費的空間。MYISAM還有一些有用的擴展,例如用來修復數據庫文件的MYISAMCHK工具和用來恢復浪費空間的MYISAMPACK工具。

    MYISAM強調了快速讀取操做,這可能就是爲何MYSQL受到了WEB開發如此青睞的主要緣由:在WEB開發中你所進行的大量數據操做都是讀取操做。因此,大多數虛擬主機提供商和INTERNET平臺提供商只容許使用MYISAM格式。

     HEAP

  HEAP容許只駐留在內存裏的臨時表格。駐留在內存使得HEAP比ISAM和MYISAM的速度都快,可是它所管理的數據是不穩定的,並且若是在關機以前沒有進行保存,那麼全部的數據都會丟失。在數據行被刪除的時候,HEAP也不會浪費大量的空間,HEAP表格在你須要使用SELECT表達式來選擇和操控數據的時候很是有用。要記住,用完表格後要刪除表格。 

    INNODB和BERKLEYDB

  INNODB和BERKLEYDB(BDB)數據庫引擎都是造就MYSQL靈活性的技術的直接產品,這項技術就是MySql++ API。在使用MySql的時候,你所面對的每個挑戰幾乎都源於ISAM和MYIASM數據庫引擎不支持事務處理也不支持外來鍵。儘管要比ISAM和MYISAM引擎慢不少,可是INNODB和BDB包括了對事務處理和外來鍵的支持,這兩點都是前兩個引擎所沒有的。如前所述,若是你的設計須要這些特性中的一者或者二者,那你就要被迫使用後兩個引擎中的一個了。


 

12.makefile嗎,一個文件依賴庫a,庫a依賴庫b,寫makefile的時候,a要放在b的前面仍是後面

  • Makefile概述:

什麼是makefile?或許不少Winodws的程序員都不知道這個東西,由於那些Windows的IDE都爲你作了這個工做,但我以爲要做一個好的和professional的程序員,makefile仍是要懂。這就好像如今有這麼多的HTML的編輯器,但若是你想成爲一個專業人士,你仍是要了解HTML的標識的含義。特別在Unix下的軟件編譯,你就不能不本身寫makefile了,會不會寫makefile,從一個側面說明了一我的是否具有完成大型工程的能力。

由於,makefile關係到了整個工程的編譯規則。一個工程中的源文件不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些文件須要先編譯,哪些文件須要後編譯,哪些文件須要從新編譯,甚至於進行更復雜的功能操做,由於makefile就像一個Shell腳本同樣,其中也能夠執行操做系統的命令。

makefile帶來的好處就是——「自動化編譯」,一旦寫好,只須要一個make命令,整個工程徹底自動編譯,極大的提升了軟件開發的效率。make是一個命令工具,是一個解釋makefile中指令的命令工具,通常來講,大多數的IDE都有這個命令,好比:Delphi的make,Visual C++的nmake,Linux下GNU的make。可見,makefile都成爲了一種在工程方面的編譯方法。

如今講述如何寫makefile的文章比較少,這是我想寫這篇文章的緣由。固然,不一樣產商的make各不相同,也有不一樣的語法,但其本質都是在「文件依賴性」上作文章,這裏,我僅對GNU的make進行講述,個人環境是RedHat Linux 8.0,make的版本是3.80。必竟,這個make是應用最爲普遍的,也是用得最多的。並且其仍是最遵循於IEEE 1003.2-1992 標準的(POSIX.2)。

在這篇文檔中,將以C/C++的源碼做爲咱們基礎,因此必然涉及一些關於C/C++的編譯的知識,相關於這方面的內容,還請各位查看相關的編譯器的文檔。這裏所默認的編譯器是UNIX下的GCC和CC。

  • 編譯和鏈接:

編譯:

定義:通常來講,不管是C、C++、仍是pas,首先要把源文件編譯成中間代碼文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,這個動做叫作編譯(compile)。

描述:編譯時,編譯器須要的是語法的正確,函數與變量的聲明的正確。只要全部的語法正確,編譯器就能夠編譯出中間目標文件。通常來講,每一個源文件都應該對應於一箇中間目標文件(O文件或是OBJ文件)。

鏈接:

定義:而後再把大量的Object File合成執行文件,這個動做叫做連接(link)。

描述:一般是你須要告訴編譯器頭文件的所在位置(頭文件中應該只是聲明,而定義應該放在C/C++文件中),連接時,主要是連接函數和全局變量,因此,咱們可使用這些中間目標文件(O文件或是OBJ文件)來連接咱們的應用程序。連接器並無論函數所在的源文件,只管函數的中間目標文件(Object File),在大多數時候,因爲源文件太多,編譯生成的中間目標文件太多,而在連接時須要明顯地指出中間目標文件名,這對於編譯很不方便,因此,咱們要給中間目標文件打個包,在Windows下這種包叫「庫文件」(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

總結一下,源文件首先會生成中間目標文件,再由中間目標文件生成執行文件。在編譯時,編譯器只檢測程序語法,和函數、變量是否被聲明。若是函數未被聲明,編譯器會給出一個警告,但能夠生成Object File。而在連接程序時,連接器會在全部的Object File中找尋函數的實現,若是找不到,那到就會報連接錯誤碼(Linker Error),在VC下,這種錯誤通常是:Link 2001錯誤,意思說是說,連接器未能找到函數的實現。你須要指定函數的Object File.

  •  Makefile

make命令執行時,須要一個 Makefile 文件,以告訴make命令須要怎麼樣的去編譯和連接程序。

首先,咱們用一個示例來講明Makefile的書寫規則。咱們的規則是:
1)若是這個工程沒有編譯過,那麼咱們的全部C文件都要編譯並被連接。
2)若是這個工程的某幾個C文件被修改,那麼咱們只編譯被修改的C文件,並連接目標程序。
3)若是這個工程的頭文件被改變了,那麼咱們須要編譯引用了這幾個頭文件的C文件,並連接目標程序。

只要咱們的Makefile寫得夠好,全部的這一切,咱們只用一個make命令就能夠完成,make命令會自動智能地根據當前的文件修改的狀況來肯定哪些文件須要重編譯,從而本身編譯所須要的文件和連接目標程序。

 

  Makefile的規則:

  target…:dependecies…

    command

target也就是一個目標文件,能夠是Object File,也能夠是執行文件。還能夠是一個標籤(Label),對於標籤這種特性,在後續的「僞目標」章節中會有敘述。
dependicies就是,要生成那個target所須要的文件或是目標。
command也就是make須要執行的命令。(任意的Shell命令)
這是一個文件的依賴關係,也就是說,target這一個或多個的目標文件依賴於dependicies中的文件,其生成規則定義在command中。說白一點就是說,dependicies中若是有一個以上的文件比target文件要新的話,command所定義的命令就會被執行。這就是Makefile的規則。也就是Makefile中最核心的內容。(深刻探討makefile

  注意事項:

  1.命令要以[Tab]爲開始

  2.有clean

  

相關文章
相關標籤/搜索