1. 程式人生 > >標頭檔案中能否進行函式的定義

標頭檔案中能否進行函式的定義

通常我們使用標頭檔案時都是在標頭檔案中進行宣告,在原始檔中定義,哪我們能否在標頭檔案中進行函式的定義
  • 我們先進行一個測試,先宣告一個test.h和一個test.cpp檔案,並且在test.h中定義一個函式和一個變數
    在這裡插入圖片描述

可以發現,程式執行沒有問題,結果也正確

  • 再建立一個test.cpp檔案,並且同時包含tset.h,再次執行
    在這裡插入圖片描述

此時程式執行出錯,提示出現了重定義的錯誤
可能有的同學會疑惑,不是已經使用了預處理指令來防止標頭檔案重複包含了嗎

#ifndef __TEST_H__
#define __TEST_H__
//標頭檔案中的內容
#endif

使用此預處理指令的作用

防止標頭檔案被重複包含,假如一個原始檔中同時包含了標頭檔案a和標頭檔案b ,而標頭檔案b中又包含a,哪在標頭檔案展開時就會包含兩份的標頭檔案a,這樣不僅浪費時間還降低了效率,而引入預處理指令就很好的解決了這個問題。

預處理的時間與範圍

編譯器在執行一個程式時大該分為以下幾個步驟

預處理階段:

預處理階段是編譯前的準備工作主要進行一下幾個工作:

  • 執行預處理指令
  • 將標頭檔案展開 即#include 所包含的內容
  • 進行巨集替換
  • 刪除註釋
  • 新增行號和標識

注意:預處理階段是各個原始檔獨自進行的,每個原始檔互不干擾,預處理的作用範圍僅在本檔案中,在預處理完成後,會生成一個.i檔案,而這個.i檔案就是下一階段編譯時的一個編譯單元,在此之後標頭檔案就已經沒有任何的作用了

編譯

編譯時是以編譯單元為單位基本單位進行的,而編譯單元即為預處理結束後每個.cpp 檔案生成的.i 檔案,編譯時有以下工作

  • 函式和變數宣告的檢查
  • 語法分析 語義分析 詞法分析 符號彙總等
  • 將程式碼轉換為組合語言

注意:編譯時僅對函式和變數的宣告進行檢查,而不關心函式的定義,編譯是以一個個單獨的檔案為單元的,與預處理一樣,而這些編譯單元都是之前生成的.i 檔案。編譯完成後會生成.s檔案

彙編

將編譯後生成的.s檔案進行彙編

  • 轉成機器指令
  • 生成符號表
  • 符號地址與地址重定位

在編譯時會將地址用符號替代,編譯器會將其翻譯成虛擬地址
而地址的重定位就是建立實體記憶體與虛擬地址間的對映
在經過彙編階段後,每個.s會生成一個對應的二進位制的.o 檔案

連結
  • 在編譯完成後,生成了目標檔案,此時就需要將這些目標檔案連結起來生成可執行文的.exe件。
通過以上的編譯器處理程式的過程,我們可以知道在連結之前,各個檔案都是相互獨立的,互不干擾。

此時,我們也就知道

  • 標頭檔案是在預處理時起作用,而它的作用範圍僅在當前檔案
  • 編譯時對於函式的定義並不關心,只關心宣告,作用範圍也僅在當前檔案。
  • 而連結時才會將個個檔案相互關聯
那麼之前的問題也就有答案了

因為在標頭檔案中定義了一個函式,在預處理時標頭檔案展開,每個檔案都有了一個該函式的定義,因為編譯時是分隔的,所以到連結時,將所有檔案關聯在一起時,發現每個包含了該標頭檔案的檔案中有一個相同函式的宣告,編譯器就會報出重定義的錯誤。

當只有一個原始檔時,因為沒有別的原始檔的衝突所以不會報錯

總結

  • 宣告是在編譯時處理
  • 定義是在連結時處理
  • 編譯之前所有檔案是隔離的

所以在標頭檔案中儘量不要進行函式的定義,只對其進行宣告。否則如果有多個原始檔連結時會報錯