1. 程式人生 > >常見C++面試題及基本知識點總結

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

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

定義:

結構體struct:把不同型別的資料組合成一個整體,自定義型別。

共同體union:使幾個不同型別的變數共同佔用一段記憶體。

地址:

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。

在標準c++中,int的定義長度要依靠你的機器的字長,也就是說,如果你的機器是32位的,int的長度為32位,如果你的機器是64位的,那麼int的標準長度就是64位。

從上面的一段文字中,我們可以看出,首先根據結構體內部成員的自身對齊值得到結構體的自身對齊值(內部成員最大的長度),如果沒有修改系統設定的預設補齊長度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)初始化:未經初始化的全域性靜態變數會被程式自動初始化為0(自動物件的值是任意的,除非他被顯示初始化)

  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.抽象類不可以定義物件。

補充一下多重繼承和虛繼承:

多重繼承:

定義:派生類繼承多個基類,派生類為每個基類(顯式或隱式地)指定了訪問級別——public、protected 或 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的底層實現主要是由紅黑樹實現的。

紅黑樹:

性質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.函式呼叫和系統呼叫的區別。

所謂系統呼叫就是使用者在程式中呼叫作業系統所提供的一個子功能,也就是系統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中最核心的內容。(

相關推薦

常見C++試題基本知識點總結

1. 結構體和共同體的區別。 定義: 結構體struct:把不同型別的資料組合成一個整體,自定義型別。 共同體union:使幾個不同型別的變數共同佔用一段記憶體。 地址: struct和union都有記憶體對齊,結構體的記憶體佈局依賴於CPU、作業系統、編譯

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

原帖:http://www.cnblogs.com/LUO77/p/5771237.html 1. 結構體和共同體的區別。 定義: 結構體struct:把不同型別的資料組合成一個整體,自定義型別。 共同體union:使幾個不同型別的變數共同佔用一段記憶體。 地址

C# 試題答案31-60

ron 保護 靜態方法 方法 重載 可空類型 ast RoCE lis 2018/8/4 c# 面試系列續 31) “System.Array.Clone()” 和 “System.Array.CopyTo()” 有何不同?   1、“CopyTo()” 從一個數組復制

面試必備,java常見基礎試題答案整理!

大家好,今天為大家帶來了常見的面試題整理的分享。 1、面向物件的特徵有哪些方面 1.抽象: 抽象就是忽略一個主題中與當前目標無關的那些方面,以便更充分地注意與當前目標有關的方面。抽象並不打算了解全部問題,而只是選擇其中的一部分,暫時不用部分細節。抽象包括兩個方面,一是過程抽象,二是

HTML5常見試題,基礎知識點

結構 cli 外部 oid chrom tor 沒有 設計 nod HTML5常見的面試題 一、HTML

面試必備,java常見基礎試題答案

1、面向物件的特徵有哪些方面 1.抽象: 抽象就是忽略一個主題中與當前目標無關的那些方面,以便更充分地注意與當前目標有關的方面。抽象並不打算了解全部問題,而只是選擇其中的一部分,暫時不用部分細節。抽象包括兩個方面,一是過程抽象,二是資料抽象。 2.繼承: 繼承是一種聯結類的層次模

98道常見Hadoop試題答案解析

1.3 下列哪個程式通常與 NameNode 在一個節點啟動?a)SecondaryNameNodeb)DataNodec)TaskTrackerd)Jobtracker答案 D,此題分析:hadoop 的叢集是基於 master/slave 模式,namenode 和 jobtracker 屬於 mast

常見Android試題答案(詳細整理)

以下是一些常用的Android面試題及答案,有需要沒需要都可以備著,希望能幫到大家。 1. 請描述一下Activity 生命週期。 答: 如下圖所示。共有七個周期函式,按順序分別是: onCreate(), onStart(), onRestart(), onResu

redis常見試題答案

memcach 也會 收集 空間 失效 java 特性 消息 切換 1、什麽是Redis? 2、Redis相比memcached有哪些優勢? 3、Redis支持哪幾種數據類型? 4、Redis主要消耗什麽物理資源? 5、Redis的全稱是什麽? 6、Redis有哪

c# UTF-8解碼編碼陣列與List<string>之間轉換等基本知識點總結

Encoding utf8 = Encoding.UTF8; //首先用utf-8進行解碼                 &

JavaScript基本試題答案

對象 con cnblogs typeof fin 如果 defined 輸出結果 是什麽 1、使用typeof bar==="object"來確定bar是否是對象的潛在陷阱是什麽?如何避免這個陷阱? 例: var bar=null; console.log(typeof

C#經典試題答案

list add 字段 有一個 副本 udp 隔離性 垃圾回收 readonly 字節 1.請你說說.net 中類和結構的區別? 答:結構和類具有大體的語法,但是結構受到的限制比類多。結構不能聲明默認的的構造函數,為結構的副本是編譯器創建和銷毀的,所以不需要默認的構造函數和

2018秋招C/C++試題總結

博主從8月中旬開始大大小小面試了十幾家公司,至今也許是告一段落吧,希望後面會有好結果,因此總結記錄一些C/C++方向常見的問題。和大家一起學習! 參考了網際網路的各種資源,自己嘗試歸類整理,謝謝~ 一、C和C++的區別是什麼? C是面向過程的語言,C++是在C語言的

關於多執行緒的最常見試題總結

如果不會這幾道多執行緒基礎題,請自覺面壁! 簡述執行緒,程式、程序的基本概念。以及他們之間關係是什麼? 執行緒與程序相似,但執行緒是一個比程序更小的執行單位。一個程序在其執行的過程中可以產生多個執行緒。與程序不同的是同類的多個執行緒共享同一塊記憶體空間和一組系統資源,所以

【不定期更新】FPGA/IC崗位常見筆試試題總結

1 數字IC(ASIC)設計流程:   規格定製、詳細設計、HDL編碼、模擬驗證、邏輯綜合(產生網表)、靜態時序分析(STA)、形式驗證(對比綜合後網表與HDL設計功能是否一致)。之後包括佈局佈線(進行硬體模組和連線資源對映)等操作,最終生成驗證後的版圖檔案用於流片。其中綜合是劃分IC設計前端和後端的界限。

百度,阿里,搜狐公司社招試題總結

從別人那裡找來的他被問到的問題,在這裡做整理總結。 一、面試遇到的問題         1.百度   面試官自帶電腦,整個面試過程都在記錄,首先詳細詢問了最近一份工作專案的架構和工作內容,

Java 常考試題答案(吐血總結)持續更新...

1、解釋Java面向物件的特徵: 抽象、封裝、繼承、多型。 2、面向物件的好處是什麼? 3、Java常用的關鍵字、修飾符的使用 4、Java中的引數傳遞(值傳遞、引用傳遞) 5、簡述內部類、靜態內部類、匿名內部類的區別 6、try catch finally的

JavaWeb總結常見試題

Http 1.網路程式設計要解決的兩個主要問題   (1)如何準確的定位網路上的一臺或者多臺主機   (2)找到主機後如何可靠的進行有效的資料傳輸 2.IP負責網路主機的定位   TCP提供可靠的或是非可靠的資料傳輸機制 3.OSI七層模型

SpringMVC總結常見試題

SpringMVC 1.SpringMVC執行流程 https://www.jianshu.com/p/8a20c547e245    (1)使用者將請求傳送到前端控制器    (2)前端控制器請求處理器對映器查詢Handler(處理器)

資料庫總結常見試題

資料庫 1.SQL *Plus 使用命令   sqlplus 使用者名稱 密碼   登入   sql /as sysdba  登入管理員 2.Orcle 預設埠1521 MySql 3306 3.三類sql語句  (