日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

【C 语言】编译过程 分析 ( 预处理 | 编译 | 汇编 | 链接 | 宏定义 | 条件编译 | 编译器指示字 )

發(fā)布時(shí)間:2025/6/17 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【C 语言】编译过程 分析 ( 预处理 | 编译 | 汇编 | 链接 | 宏定义 | 条件编译 | 编译器指示字 ) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

相關(guān)文章鏈接 :
1.【嵌入式開(kāi)發(fā)】C語(yǔ)言 指針數(shù)組 多維數(shù)組
2.【嵌入式開(kāi)發(fā)】C語(yǔ)言 命令行參數(shù) 函數(shù)指針 gdb調(diào)試
3.【嵌入式開(kāi)發(fā)】C語(yǔ)言 結(jié)構(gòu)體相關(guān) 的 函數(shù) 指針 數(shù)組
4.【嵌入式開(kāi)發(fā)】gcc 學(xué)習(xí)筆記(一) - 編譯C程序 及 編譯過(guò)程
5.【C語(yǔ)言】 C 語(yǔ)言 關(guān)鍵字分析 ( 屬性關(guān)鍵字 | 常量關(guān)鍵字 | 結(jié)構(gòu)體關(guān)鍵字 | 聯(lián)合體關(guān)鍵字 | 枚舉關(guān)鍵字 | 命名關(guān)鍵字 | 雜項(xiàng)關(guān)鍵字)


  • 一 編譯過(guò)程
    • 編譯過(guò)程圖解
    • 步驟1 編譯預(yù)處理
      • 1 預(yù)編譯處理內(nèi)容
      • 2 預(yù)編譯處理代碼示例 驗(yàn)證 include define 注釋 處理過(guò)程
    • 步驟2 編譯
      • 1 編譯 中的操作
      • 2 編譯 示例
    • 步驟3 匯編
      • 1 匯編 中的操作
      • 2 匯編 示例
    • 單步編譯 示例 預(yù)處理 編譯 匯編 鏈接
    • 鏈接器
      • 1 鏈接器簡(jiǎn)介
      • 2 靜態(tài)鏈接
      • 3 動(dòng)態(tài)鏈接
  • 二 宏定義 使用詳解
    • 宏定義 常量
    • 宏表達(dá)式
      • 1 宏表達(dá)式簡(jiǎn)介
      • 2 宏表達(dá)式 代碼示例
      • 3 宏表達(dá)式 與 函數(shù)對(duì)比
    • 宏表達(dá)式 或 宏常量 作用域限制
      • 1 宏定義 沒(méi)有作用域限制
      • 2 undef 限制宏定義 作用域
    • 內(nèi)置宏
      • 1 內(nèi)置宏 簡(jiǎn)介
      • 2 日志宏 代碼示例
  • 三 條件編譯
    • 基本概念
      • 1 條件編譯簡(jiǎn)介
      • 2 條件編譯 示例 簡(jiǎn)單的條件編譯 修改代碼實(shí)現(xiàn)
      • 3 條件編譯 示例 使用命令行生成宏定義控制條件編譯 不修改代碼實(shí)現(xiàn)
    • include 間接包含
      • 1 間接包含 介紹
      • 2 include 間接包含 示例 錯(cuò)誤示例 解決方案
      • 2 include 間接包含 示例 正確的處理方法
    • 條件編譯控制示例 編譯不同產(chǎn)品 控制開(kāi)發(fā)版本和發(fā)布版本編譯
  • 四 編譯指示字 error line
    • error 編譯指示字
      • 1 error 簡(jiǎn)介
      • 2 error warning 代碼示例
    • line 編譯指示字
      • 1 line 簡(jiǎn)介
      • 2 line 代碼示例
    • pragma 編譯器指示字
      • 1 pragma 簡(jiǎn)介
      • 2 pragma message 參數(shù)
      • 3 pragma pack 參數(shù)
  • 五 運(yùn)算符
    • 運(yùn)算符
    • 運(yùn)算符






一. 編譯過(guò)程




1. 編譯過(guò)程圖解


編譯過(guò)程 :


編譯過(guò)程 : 預(yù)處理 -> 編譯 -> 匯編 -> 鏈接;
1. 編譯預(yù)處理 : 產(chǎn)生 .i 后綴的預(yù)處理文件;
2. 編譯操作 : 產(chǎn)生 .s 后綴的匯編文件;
3. 匯編操作 : 產(chǎn)生 .o 后綴的機(jī)器碼二進(jìn)制文件;
4. 鏈接操作 : 產(chǎn)生可執(zhí)行文件 ;





2. 步驟1 : 編譯預(yù)處理


(1) 預(yù)編譯處理內(nèi)容


預(yù)編譯操作 :

  • 1.處理注釋 : 刪除所有的注釋, 使用空格取代注釋內(nèi)容;
  • 2.處理宏定義 : 刪除所有的 #define 宏定義, 替換 代碼中 宏定義 對(duì)應(yīng) 的 內(nèi)容;
  • 3.處理?xiàng)l件編譯指令 : 處理 #if, #else, #ifdef, #elif, #endif 等條件編譯指令 ;
  • 4.處理#include : 處理 #include, 將被包含的文件拷貝到代碼中.
  • 5.處理#pragma : 編譯器使用的 # program 指令 保留下來(lái), 這個(gè)指令是 C 代碼 到 匯編 代碼 進(jìn)行 處理的指示字.


預(yù)處理指令 : gcc -E test_1.c -o test_1.i



(2) 預(yù)編譯處理代碼示例 (驗(yàn)證 #include | #define | 注釋 處理過(guò)程)


編譯預(yù)處理示例 :

  • 1.代碼示例 :
//預(yù)編譯會(huì)將 stdio.h 中的內(nèi)容拷貝到代碼中, #include <stdio.h>//注釋會(huì)被替換成空格 //預(yù)編譯中, HELLO_WOLD 會(huì)被原封不動(dòng)的在代碼中被替換為 "Hello World\n" #define HELLO_WOLD "Hello World\n" int main() {printf(HELLO_WOLD);return 0; }
  • 2.預(yù)處理 : 使用 gcc -E test_1.c -o test_1.i 命令進(jìn)行預(yù)處理, 預(yù)處理完之后生成 test_1.i 文件.
  • 3.查看預(yù)處理文件 : 查看 test_1.i 文件 ;


test_1.i 出現(xiàn)了800多行的預(yù)處理文件, 原因是 #include < stdio.h >, 將 stdio.h 的文件拷貝了進(jìn)來(lái), 如果去掉了 #include 聲明, 那么預(yù)處理文件就很小.


刪除了 # include 代碼 :

  • 1.代碼示例 :
  • 2.預(yù)處理 : 使用 gcc -E test_1.c -o test_1.i 命令進(jìn)行預(yù)處理;
  • 3.查看預(yù)處理文件 :


如果沒(méi)有了 #include 聲明, 那么預(yù)編譯后的文件會(huì)大大減少.





3. 步驟2 : 編譯


(1) 編譯 中的操作


編譯 步驟中的操作 :

  • 1.詞法分析 : 分析 關(guān)鍵字, 標(biāo)識(shí)符, 立即數(shù) 的合法性;
  • 2.語(yǔ)法分析 : 檢查 代碼 是否遵循 C 語(yǔ)言語(yǔ)法規(guī)則;
  • 3.語(yǔ)義分析 : 分析表達(dá)式是否合法;


編譯 需要的指令 : gcc -S test_1.c -o test_1.s ;



(2) 編譯 示例


編譯 示例 :

  • 1.代碼內(nèi)容 :
//預(yù)編譯會(huì)將 stdio.h 中的內(nèi)容拷貝到代碼中, //如果刪除了 include 預(yù)編譯, 那么代碼量會(huì)大大減少 #include <stdio.h>//注釋會(huì)被替換成空格 //預(yù)編譯中, HELLO_WOLD 會(huì)被原封不動(dòng)的在代碼中被替換為 "Hello World\n" #define HELLO_WOLD "Hello World\n" int main() {printf(HELLO_WOLD);return 0; }
  • 2.執(zhí)行編譯內(nèi)容 : 執(zhí)行 gcc -S test_1.c -o test_1.s 命令, 得到 test_1.o 文件.
  • 3.查看編譯結(jié)果 : 查看生成的 test_1.s 文件, 是一個(gè)匯編文件 ;




4. 步驟3 : 匯編


(1) 匯編 中的操作


匯編 操作 :

  • 1.執(zhí)行者 : 匯編器;
  • 2.操作 : 使用 匯編器 將 匯編代碼, 轉(zhuǎn)化為 機(jī)器可執(zhí)行的 機(jī)器碼.


匯編 命令 : gcc -c test_1.s -o test_1.o ;
每條匯編指令都對(duì)應(yīng)著指定的機(jī)器碼 .



(2) 匯編 示例


匯編 過(guò)程示例 :

  • 1.代碼內(nèi)容 :
//預(yù)編譯會(huì)將 stdio.h 中的內(nèi)容拷貝到代碼中, //如果刪除了 include 預(yù)編譯, 那么代碼量會(huì)大大減少 #include <stdio.h>//注釋會(huì)被替換成空格 //預(yù)編譯中, HELLO_WOLD 會(huì)被原封不動(dòng)的在代碼中被替換為 "Hello World\n" #define HELLO_WOLD "Hello World\n" int main() {printf(HELLO_WOLD);return 0; }
  • 2.執(zhí)行編譯內(nèi)容 : 執(zhí)行 gcc -S test_1.c -o test_1.s 命令, 得到 test_1.o 文件.
  • 3.查看編譯結(jié)果 : 查看生成的 test_1.s 文件, 是一個(gè)匯編文件 ;
  • 4.匯編 : 執(zhí)行 gcc -c test_1.s -o test_1.o 命令, 得到 test_1.o 文件 ;




5. 單步編譯 示例 ( 預(yù)處理 | 編譯 | 匯編 | 鏈接)



單步編譯示例 :

  • 1.代碼結(jié)構(gòu): 頭文件 test_1.h, 代碼文件 test_1.c ;
  • 2.頭文件代碼 :
//定義宏, 在預(yù)編譯中會(huì)被刪除, 直接替換到代碼中 //預(yù)編譯過(guò)程中 MIN(a,b) 會(huì)被 (((a)>(b)) ? (b) : (a)) 替換 #define MIN(a,b) (((a)>(b)) ? (b) : (a))//定義全局變量 int global_variable = 666;
  • 3.主要邏輯代碼 :
#include "test_1.h"//定義兩個(gè)宏 #define SMALL 666 #define BIG 888int min(int a, int b) {//在預(yù)編譯的步驟中, MIN(a, b) 直接替換為 (((a)>(b)) ? (b) : (a))return MIN(a,b); }int main() {//預(yù)編譯過(guò)程中, SMALL 被替換成 666, BIG 被替換成 888int min_number = min(SMALL, BIG); // Call max to get the larger numberreturn 0; }
  • 4.進(jìn)行預(yù)編譯 : 執(zhí)行 gcc -E test_1.c -o test_1.i 指令, 會(huì)生成 test_1.i 文件;
  • 5.預(yù)編譯目標(biāo)結(jié)果文件 : test_1.i 文件;

    分析 test_1.i 文件:

  • 拷貝包含文件 : #include “test_1.h” 直接將 test_1.h 中的內(nèi)容拷貝到 test_1.i 文件中 , 8 ~ 13 行是 test_1.h 文件拷貝到 test_1.i 中的內(nèi)容.
  • 編譯器注釋說(shuō)明 : #部分不管, 是編譯器生成的說(shuō)明 ;
  • 處理注釋 : 將注釋使用空格替換, test_1.i 中 8 ~ 12 行 5 行是空格, 第 8, 9, 12 行對(duì)應(yīng)著 test_1.h 中的注釋, 第十行對(duì)應(yīng)著 test_1.h 中的宏定義, 第11行對(duì)應(yīng)著空白行.
  • 替換宏定義 : 將宏定義的位置替換到代碼中, 宏定義行使用空格替代 , 其中 8 ~ 12 行空行, 第10行就是宏定義刪除后的空行 ; 代碼中 MIN(a,b) 的位置 被 (((a)>(b)) ? (b) : (a)) 替換, SMALL 被 666 替換, BIG 被 888 替換.
  • 6.編譯 產(chǎn)生 匯編文件 : 執(zhí)行 gcc -S test_1.i -o test_1.s 命令 , 生成了 test_1.s 文件,

  • 7.將匯編文件轉(zhuǎn)為機(jī)器碼 : 執(zhí)行指令 gcc -C test_1.s -o test_1.o , 生成 test_1.o 文件 , 生成的機(jī)器碼是二進(jìn)制的文件, 使用 文本編輯器打不開(kāi), 在 Windows 中使用 010Editer 打開(kāi)查看二進(jìn)制內(nèi)容 ;






6. 鏈接器


(1) 鏈接器簡(jiǎn)介


鏈接器簡(jiǎn)介 :

  • 1.銜接模塊引用 : 軟件各個(gè)模塊之前會(huì)相互調(diào)用, 鏈接器就是處理這些相互引用的位置之間的銜接 .


鏈接器 模塊拼裝 :

  • 1.普通鏈接 : 運(yùn)行時(shí), 將所有的代碼庫(kù) .o 文件, 一次性拷貝到內(nèi)存中, 如果運(yùn)行多個(gè)副本, 那么相同的代碼庫(kù)會(huì)各自占用一部分內(nèi)存, 這些內(nèi)存中存儲(chǔ)的東西是一樣的.
  • 2.靜態(tài)鏈接 : 出于節(jié)省內(nèi)存的考慮, 我們可以將相同的代碼封裝到靜態(tài)庫(kù)中, 那么多個(gè)副本同時(shí)運(yùn)行時(shí), 只加載一份靜態(tài)庫(kù)即可, 這樣相對(duì)于普通鏈接來(lái)說(shuō)節(jié)省內(nèi)存, 內(nèi)存消耗比動(dòng)態(tài)鏈接要多.
  • 3.動(dòng)態(tài)鏈接 : 運(yùn)行開(kāi)始的時(shí)候只加載必要的模塊, 當(dāng)開(kāi)始調(diào)用某一動(dòng)態(tài)鏈接庫(kù)時(shí), 才去尋找并加載動(dòng)態(tài)鏈接庫(kù)到內(nèi)存中, 節(jié)省內(nèi)存, 但是運(yùn)行效率慢.


(2) 靜態(tài)鏈接


靜態(tài)鏈接 :

  • 1.加載時(shí)機(jī) : 靜態(tài)庫(kù)中的代碼, 在運(yùn)行開(kāi)始前就全部加載到內(nèi)存中, 這與動(dòng)態(tài)鏈接中
  • 2.加載份數(shù) : 在內(nèi)存中, 靜態(tài)庫(kù)只加載一次, 第一次執(zhí)行程序用到靜態(tài)庫(kù)時(shí), 加載靜態(tài)庫(kù), 當(dāng)再次運(yùn)行時(shí), 繼續(xù)復(fù)用第一次加載靜態(tài)庫(kù), 這樣比較節(jié)省內(nèi)存.


靜態(tài)鏈接圖示 :

當(dāng)運(yùn)行2個(gè)a.out 時(shí), 對(duì)于靜態(tài)庫(kù) test_3.a 只需要加載 1 次, 但是對(duì)于 test_1.o 和 test_2.o 需要各自加載一次.



靜態(tài)庫(kù)鏈接內(nèi)存圖 :



(3) 動(dòng)態(tài)鏈接


動(dòng)態(tài)鏈接 :

  • 1.加載時(shí)機(jī) : 程序運(yùn)行時(shí)不加載動(dòng)態(tài)鏈接庫(kù), 程序執(zhí)行調(diào)用動(dòng)態(tài)鏈接庫(kù)函數(shù)的時(shí)候, 才動(dòng)態(tài)的加載動(dòng)態(tài)鏈接庫(kù) .
  • 2.執(zhí)行效率 : 動(dòng)態(tài)鏈接效率 比 靜態(tài)鏈接要低, 因?yàn)槠鋱?zhí)行的時(shí)候, 需要搜索并加載動(dòng)態(tài)鏈接, 這樣會(huì)消耗一定的性能 ;


動(dòng)態(tài)鏈接圖解 :







二. 宏定義 使用詳解




1. 宏定義 常量



宏定義 常量 :

  • 1.#define 定義 常量, 只是進(jìn)行簡(jiǎn)單的代碼替換.
  • 2.#define 定義的不是真正意義的常量, 只是進(jìn)行簡(jiǎn)單的代碼替換, 下面代碼中的內(nèi)容都是合法的.
//下面的宏定義都是合法的 //在預(yù)編譯界面都是進(jìn)行簡(jiǎn)單的代碼文本替換 #define YES 1 #define PI 3.14 #define COUNTRY "China"//出現(xiàn) NAME 的位置使用 Bill 替換 #define NAME Bill //這條宏定義是合法的, \ 是接續(xù)符號(hào) #define PATH \root\apue\ io_code




2. 宏表達(dá)式


(1) 宏表達(dá)式簡(jiǎn)介


宏表達(dá)式 #define :

  • 1.本質(zhì)不是函數(shù) : 使用 # define 表達(dá)式, 有函數(shù)的假象, 但是其并不是函數(shù);
  • 2.功能強(qiáng)大, 但容易出錯(cuò) : 某些用法 生硬的替換代碼 可能導(dǎo)致出現(xiàn) 出錯(cuò)的情況.
  • 3.宏使用示例 :
#include <stdio.h>//宏定義表達(dá)式 加法表達(dá)式 #define SUM(a,b) (a)+(b) //宏定義表達(dá)式 獲取兩個(gè)數(shù)之間較小的值 #define MIN(a,b) ((a<b)?a:b) //宏定義表達(dá)式 獲取數(shù)組中元素測(cè)試 #define DIM(array) (sizeof(array)/sizeof(*array))//對(duì)比 #define SUM(a,b) (a)+(b) 宏定義, 方法不容易出現(xiàn)歧義 int sum(int a, int b) {return a + b; }//使用函數(shù)計(jì)算數(shù)組大小, 下面的語(yǔ)句是無(wú)法實(shí)現(xiàn)的 //array 傳入之后, 在函數(shù)中會(huì)退化成一個(gè)指針, 其大小與元素大小一樣 //sizeof(array) 是指針?biāo)加玫目臻g大小, 不是數(shù)組所占用的空間大小 int dim(int array[]) {return sizeof(array)/sizeof(*array); }int main() {//獲取 333 和 666 的和printf("%d\n", SUM(333, 666));//獲取 333 666 之間較小的值printf("%d\n", MIN(333, 666));//這里我們想要得到 3 * 3 即 9, 但是編譯執(zhí)行后 結(jié)果是 5//即使用 SUM(1,2) 替換為 (1)+(2)//預(yù)編譯后語(yǔ)句變?yōu)?: printf("%d\n", (1)+(2) * (1)+(2));//注意點(diǎn)1 : 不要將宏表達(dá)式連續(xù)使用printf("%d\n", SUM(1, 2) * SUM(1, 2));//MIN(a++, b) 打印結(jié)果是 2//如果出現(xiàn)了 a++ 等自增符號(hào)被宏替換//預(yù)編譯后替換結(jié)果 : printf("%d\n", ((a++<b)?a++:b));//注意點(diǎn)2 : 不要在宏替換中寫(xiě) 自增 自減 等其他表達(dá)式, 只使用簡(jiǎn)單的單一變量int a = 1;int b = 3;printf("%d\n", MIN(a++, b));//將 DIM(array) 宏替換, 計(jì)算數(shù)組大小, 打印結(jié)果為 7//打印的語(yǔ)句被宏替換為 : printf("%ld\n", (sizeof(array)/sizeof(*array)));//如果使用函數(shù)來(lái)計(jì)算數(shù)組大小,是無(wú)法實(shí)現(xiàn)的,如果函數(shù)傳入 array, 函數(shù)參數(shù) 會(huì)將 array 當(dāng)做一個(gè)指針, //該array 數(shù)組就退化成了一個(gè)指針, 無(wú)法計(jì)算大小了, 該功能要比函數(shù)要強(qiáng)大int array[] = {0, 1, 2, 3, 4, 5, 6};printf("%ld\n", DIM(array));//調(diào)用函數(shù)計(jì)算數(shù)組大小, 同樣的語(yǔ)句打印出來(lái)的結(jié)果是1printf("%d\n", dim(array));return 0; }
  • 4.執(zhí)行結(jié)果 :


(2) 宏表達(dá)式 代碼示例


宏替換代碼示例 :

  • 1.原始 C 代碼 (含有宏定義) :
#include <stdio.h> #include <malloc.h>//內(nèi)存分配 #define MALLOC(type, x) (type*)malloc(sizeof(type)*x) //死循環(huán) #define FOREVER() while(1)//用于替換 {} 的宏定義 #define BEGIN { #define END }//for 循環(huán) 宏 #define FOREACH(i, m) for(i = 0; i < m; i++)int main() {int array[] = {1, 2, 3};int i = 0;//使用宏替換結(jié)果 : int *p = (int*)malloc(sizeof(int)*3);int *p = MALLOC(int, 3);//普通的for循環(huán)//紅替換結(jié)果 : for(i = 0; i < 3; i++)FOREACH(i, 3)BEGINp[i] = array[i];printf("%d\n", p[i]);END//釋放分配的 p 指針空間free(p);//在此處進(jìn)行無(wú)限循環(huán)//宏替換結(jié)果 : while(1);FOREVER();//這行end永遠(yuǎn)打印不出來(lái)了printf("end\n");return 0; }
  • 2.預(yù)處理宏替換結(jié)果 : test_1.c 進(jìn)行預(yù)處理后的 test_1.i, 使用 gcc -E test_1.c -o test_1.i 命令進(jìn)行預(yù)處理;
  • 3.執(zhí)行結(jié)果 :


(3) 宏表達(dá)式 與 函數(shù)對(duì)比


宏表達(dá)式 與 函數(shù)對(duì)比 :

  • 1.對(duì)編譯器透明 : 宏表達(dá)式在預(yù)編譯階段進(jìn)行替換處理, 編譯器不知道宏的存在;
  • 2.運(yùn)算方面 : 宏替換不進(jìn)行任何運(yùn)算, 沒(méi)有實(shí)參形參的概念, 全部都是機(jī)械的替換, 宏表達(dá)式參數(shù)可以使變量,也可以是類型;
  • 3.調(diào)用開(kāi)銷方面 : 宏表達(dá)式不消耗任何調(diào)用開(kāi)銷, 沒(méi)有函數(shù)調(diào)用開(kāi)銷, 其在預(yù)處理階段就被替換了;
  • 4.關(guān)于遞歸 : 宏表達(dá)式不能使用遞歸定義宏;


遞歸代碼示例 (錯(cuò)誤示例) :

  • 1.宏遞歸代碼示例 :
#include <stdio.h>//定義一個(gè)遞歸宏, 這種定義時(shí)錯(cuò)誤的, 宏表達(dá)式中不能出現(xiàn)遞歸 #define FAC(n) ((n > 0) ? (FAC(n - 1) + n) : 0)//遞歸函數(shù) int fac(int n) {return ((n > 0) ? (fac(n - 1) + n) : 0); }int main() {//該步驟報(bào)錯(cuò)printf("%d\n", FAC(10));return 0; }
  • 2.預(yù)編譯結(jié)果 : 宏替換后的結(jié)果 ;
  • 3.編譯結(jié)果 : 編譯報(bào)錯(cuò), 提示沒(méi)有定義 FAC() 方法 ;




3. 宏表達(dá)式 或 宏常量 作用域限制


(1) 宏定義 沒(méi)有作用域限制


宏定義作用域限制 :

  • 1.宏定義位置 : 宏定義可以再程序的任意位置定義, 甚至是函數(shù)內(nèi)部;
  • 2.宏定義使用位置 : 宏定義可以再任何位置使用;
  • 3.代碼示例 :
#include <stdio.h>//宏定義 常量 和 宏定義表達(dá)式?jīng)]有作用域限制 //宏定義可以出現(xiàn)在代碼的任何位置, 定義完畢之后可以再任何位置調(diào)用int min_1(int a, int b) {//任意位置定義的宏可以在任意地方使用, 沒(méi)有作用域限制#define MIN(a, b) ((a) < (b) ? a : b)//直接宏替換為 : return ((a) < (b) ? a : b);return MIN(a, b); }int min_2(int a, int b, int c) {//直接宏替換為 : return ((((a) < (b) ? a : b)) < (c) ? ((a) < (b) ? a : b) : c);return MIN(MIN(a, b), c); }int main() {printf("%d\n", min_1(1, 2));printf("%d\n", min_2(4, 2, 3));return 0; }
  • 4.預(yù)編譯結(jié)果 :
  • 5.執(zhí)行結(jié)果 :


(2) #undef 限制宏定義 作用域


限制宏定義作用域 #undef 用法 :

  • 1.使用方法 : 定義宏 #define MIN 100 之后, 可以使用 #undef MIN 限制其作用范圍, 只能在 #define 和 #undef 之間使用該宏, 在 #undef 之后就不可使用該宏了;
  • 2.使用示例 (錯(cuò)誤示例) :
#include <stdio.h>//宏定義 常量 和 宏定義表達(dá)式?jīng)]有作用域限制 //宏定義可以出現(xiàn)在代碼的任何位置, 定義完畢之后可以再任何位置調(diào)用//#undef 可以限制 #define 作用域int min_1(int a, int b) {//任意位置定義的宏可以在任意地方使用, 沒(méi)有作用域限制#define MIN(a, b) ((a) < (b) ? a : b)//直接宏替換為 : return ((a) < (b) ? a : b);return MIN(a, b);//這里取消 MIN 宏定義, 限制其作用域只能在該范圍之內(nèi)使用, 之后就不可使用 MIN 了#undef MIN }int min_2(int a, int b, int c) {//此處無(wú)法使用 MIN 宏, 上面使用了 #undef MIN 限制了宏定義的使用范圍return MIN(MIN(a, b), c); }int main() {printf("%d\n", min_1(1, 2));printf("%d\n", min_2(4, 2, 3));return 0; }
  • 3.預(yù)編譯結(jié)果 :
  • 4.編譯報(bào)錯(cuò)內(nèi)容 :




4. 內(nèi)置宏


(1) 內(nèi)置宏 簡(jiǎn)介


內(nèi)置宏舉例 :

  • 1.__FILE__ : 代表被編譯的文件名稱 ;
  • 2.__LINE__ : 代表當(dāng)前的行號(hào) ;
  • 3.__DATE__ : 代表當(dāng)前的日期 ;
  • 4.__TIME__ : 代表編譯時(shí)的時(shí)間 ;
  • 5.__STDC__ : 編譯器是否遵循 標(biāo)準(zhǔn) C 規(guī)范 ;


(2) 日志宏 代碼示例


使用宏定義日志打印 :

  • 1.代碼示例 :
#include <stdio.h> #include <time.h>//1.日志宏, 用函數(shù)反而達(dá)不到打印日志行號(hào)的效果, 因此這里使用宏最好 #define LOG(s) printf("%s : %d : %s\n", __FILE__, __LINE__, s);//2.使用函數(shù)打印日志, 但是調(diào)用處的行號(hào)無(wú)法準(zhǔn)確顯示 //調(diào)用該函數(shù)打印出的日志, 其行號(hào)始終是函數(shù)中的行號(hào) //使用函數(shù)打印日志, 無(wú)法獲取行號(hào), 因此我們使用宏來(lái)實(shí)現(xiàn) void Log(char* s) {//__FILE__ 是內(nèi)置宏, 代表 本文件文件名//__LINE__ 是內(nèi)置宏, 代表 當(dāng)前行號(hào)printf("%s : %d : %s\n", __FILE__, __LINE__, s); }//3.嘗試打印時(shí)間的函數(shù) void log_time() {time_t t;struct tm* p;//獲取當(dāng)前時(shí)間time(&t);//時(shí)間轉(zhuǎn)換p = localtime(&t);printf("%s", asctime(p)); }//4.定義打印時(shí)間 行數(shù) 文件 名的宏日志 //多行宏定義可以使用 do{}while(0) 來(lái)實(shí)現(xiàn) #define LOG_TIME(s) do{ \time_t t; \struct tm* p; \time(&t); \p = localtime(&t); \printf("time : %sfile : %s \nline : %d\ncontent : %s\n", asctime(p), __FILE__, __LINE__, s); \ }while(0)int main() {Log("函數(shù)打印日志1");Log("函數(shù)打印日志2");LOG("宏打印日志1");LOG("宏打印日志2");log_time();LOG_TIME("日志宏帶時(shí)間");return 0; }
  • 2.運(yùn)行結(jié)果 :


日志宏 : 打印日志的同時(shí), 打印當(dāng)前的文件名稱, 代碼行號(hào), 當(dāng)前運(yùn)行時(shí)間 ;







三. 條件編譯




1. 基本概念


(1) 條件編譯簡(jiǎn)介


條件編譯指令 :

  • 1.指令 : #if , #ifdef, #ifndef, #else, #endif 等 ;
  • 2.用法 : 與 if else 等用法類似, 具體查看下面的示例, 但是 #if, #else, #endif 是預(yù)編譯階段被預(yù)編譯處理的, if else 是在編譯階段, 被編譯器處理, 是要被編譯到目標(biāo)代碼中的 ;
  • 3.作用 : 條件編譯指令是預(yù)編譯指令, 控制某段代碼是否被編譯, 可以按照不同的條件選擇性編譯指定的代碼段, 選擇性的忽略某段代碼, 用以編譯出不同功能的可執(zhí)行目標(biāo)文件 ;



條件編譯的應(yīng)用環(huán)境 :

  • 1.軟件分支維護(hù) : 維護(hù)一個(gè)軟件的不同分支, 控制軟件分支編譯;
  • 2.區(qū)分版本 : 區(qū)分軟件調(diào)試版本 和 正式上線的版本, 開(kāi)發(fā)版本肯定有很多調(diào)試信息, 正式版沒(méi)有冗余的信息;


條件編譯 注意點(diǎn) :

  • 1.命令行定義宏 : 可以使用 gcc -D 選項(xiàng)來(lái)定義宏, 如 gcc -DDEBUG test_1.c 等價(jià)于 #define DEBUG, gcc -DMIN=1 test_1.c 等價(jià)于 #define MIN 1 語(yǔ)句 ;
  • 2.條件編譯處理頭文件包含問(wèn)題 : #include 會(huì)出現(xiàn)多重嵌套問(wèn)題, 使用 #ifndef _HEAD_H_ | #define _HEAD_H_ | #endif 可以解決頭文件多次引用的問(wèn)題 ;
  • 3.使用一套代碼維護(hù)不同產(chǎn)品 : 開(kāi)發(fā)中, 可以條件編譯來(lái)維護(hù)一套代碼, 編譯出不同的產(chǎn)品 ;
  • 4.開(kāi)發(fā)板和正式版區(qū)分 : 使用條件編譯可以區(qū)分產(chǎn)品的開(kāi)發(fā)調(diào)試版本 和 正式發(fā)布版本 ;


(2) 條件編譯 示例 (簡(jiǎn)單的條件編譯 | 修改代碼實(shí)現(xiàn))


通過(guò)修改代碼 控制 條件編譯 代碼示例 :

  • 1.代碼1 :
#include <stdio.h>//預(yù)編譯階段,如果發(fā)現(xiàn) //也可以在gcc -E 命令中指定 #define 常量 //gcc -DC=1 -E test_1.c -o test_1.i 可以進(jìn)行同樣的預(yù)編譯處理, 即使沒(méi)有定義這個(gè)宏 //gcc -DC=1 test_1.c 可以在預(yù)編譯階段生成下面同樣宏定義 #define C 1int main() {#if(C == 1)printf("1\n");#elseprintf("2\n");#endifreturn 0; }
  • 2.條件編譯 預(yù)編譯結(jié)果 : 使用 gcc -E test_1.c -o test_1.i 命令進(jìn)行預(yù)編譯 ;

  • 3.執(zhí)行結(jié)果 :


修改代碼后 刪除宏定義 :

  • 1.代碼2 :
#include <stdio.h>int main() {#if(C == 1)printf("1\n");#elseprintf("2\n");#endifreturn 0; }
  • 2.條件編譯 預(yù)編譯結(jié)果 :
  • 3.執(zhí)行結(jié)果 :


上述兩個(gè)例子, 主要是通過(guò)在代碼中定義 宏常量, 來(lái)控制條件編譯中, 哪些語(yǔ)句需要編譯, 哪些語(yǔ)句在預(yù)編譯階段就要?jiǎng)h除 ;



(3) 條件編譯 示例 ( 使用命令行生成宏定義控制條件編譯 | 不修改代碼實(shí)現(xiàn))


使用命令行定義宏 從而控制條件編譯, 代碼不變 :

  • 1.代碼 :
#include <stdio.h>int main() {#if(C == 1)printf("1\n");#elseprintf("2\n");#endifreturn 0; }
  • 2.命令行1 : 使用命令行命令 gcc -DC=1 -E test_1.c -o test_1.i, 該命令 等價(jià)于 定義 宏 #define C 1, 下面是預(yù)編譯結(jié)果 和 執(zhí)行結(jié)果 ;

  • 3.命令行2 : 使用命令行命令*gcc -DC=2 -E test_1.c -o test_1.i, 該命令等價(jià)于 定義宏 #define C 2, 下面是預(yù)編譯結(jié)果 和 執(zhí)行結(jié)果 ;





2. #include 間接包含


(1) 間接包含 介紹


#include 間接包含 :

  • 1.#include作用 : #include 作用是 單純的 將 文件內(nèi)容 嵌入 到 當(dāng)前的 文件 ;
  • 2.間接包含 : #include 會(huì)有間接包含的情況, 如 包含的 文件中, 有重復(fù)包含的情況 ;


(2) #include 間接包含 示例 ( 錯(cuò)誤示例 | 解決方案 )


間接包含 結(jié)構(gòu)圖示 : test_1.c 文件包含 三個(gè)頭文件, test_1.h 包含的 test_2.h 頭文件 與 test_1.c 包含的該頭文件相同, 同一個(gè)頭文件被導(dǎo)入了2次, 因此編譯時(shí)會(huì)報(bào)錯(cuò);


間接包含 代碼示例 :

  • 1.test_1.c 代碼 :
#include <stdio.h> #include "test_1.h" #include "test_2.h"int main() {fun();printf("%s\n", HELLO);return 0; }
  • 2.test_1.h 頭文件代碼 :
#include <stdio.h> #include "test_2.h"char* HELLO = "Hello World";void fun() {printf("test_1.h Hello"); }
  • 3.test_2.h 頭文件代碼 :
int test_2_variable = 666;
  • 4.預(yù)編譯結(jié)果 : 同時(shí)拷貝了兩份 int test_2_variable = 666; 語(yǔ)句, 如果進(jìn)入編譯階段, 肯定是重復(fù)定義變量 ;
  • 5.編譯報(bào)錯(cuò)內(nèi)容 :


間接包含 簡(jiǎn)單解決方案 : 下面的代碼與上面的唯一區(qū)別是, test_1.c 中注釋掉了 #include “test_2.h” 語(yǔ)句.

  • 1.test_1.c 代碼 :
#include <stdio.h> #include "test_1.h" //#include "test_2.h"int main() {fun();printf("%s\n", HELLO);return 0; }
  • 2.test_1.h 頭文件代碼 :
#include <stdio.h> #include "test_2.h"char* HELLO = "Hello World";void fun() {printf("test_1.h Hello"); }
  • 3.test_2.h 頭文件代碼 :
int test_2_variable = 666;
  • 4.執(zhí)行結(jié)果 :


(2) #include 間接包含 示例 ( 正確的處理方法 )


使用 #ifndef , #define 和 #endif 語(yǔ)句處理頭文件包含情況 :

  • 1.主代碼 test_1.c :
#include <stdio.h>//使用了 #ifndef #endif 宏 控制編譯頭文件, 任意包含幾次頭文件都不會(huì)出錯(cuò) #include "test_1.h" #include "test_1.h" #include "test_2.h" #include "test_2.h"int main() {fun();printf("%s\n", HELLO);return 0; }
  • 2.頭文件1 test_1.h :
//如果沒(méi)有定義 _TEST_2_H_ 宏, 才擴(kuò)展下面的內(nèi)容 //如果已經(jīng)定義了 _TEST_2_H_ 宏, 那么從 #ifndef 到 #endif 之間的內(nèi)容都要擴(kuò)展進(jìn)去 //一般情況下定義的宏名稱是 頭文件變成大寫(xiě) #ifndef _TEST_1_H_ #define _TEST_1_H_#include <stdio.h> #include "test_2.h"char* HELLO = "Hello World";void fun() {printf("test_1.h Hello"); }#endif
  • 3.頭文件2 test_2.h :
//如果沒(méi)有定義 _TEST_2_H_ 宏, 才擴(kuò)展下面的內(nèi)容 //如果已經(jīng)定義了 _TEST_2_H_ 宏, 那么從 #ifndef 到 #endif 之間的內(nèi)容都要擴(kuò)展進(jìn)去 //一般情況下定義的宏名稱是 頭文件變成大寫(xiě) #ifndef _TEST_2_H_ #define _TEST_2_H_int test_2_variable = 666;#endif
  • 4.預(yù)編譯結(jié)果 :
  • 5.代碼執(zhí)行結(jié)果 :




3. 條件編譯控制示例 ( 編譯不同產(chǎn)品 | 控制開(kāi)發(fā)版本和發(fā)布版本編譯)



條件編譯控制代碼示例 :

  • 1.代碼 :
#include <stdio.h>//控制開(kāi)發(fā)版本與發(fā)布版本 : //如果定義了 DEBUG 宏, 那么LOG(s) 就會(huì)打印調(diào)用位置的文件和行號(hào)以及對(duì)應(yīng)日志 //如果沒(méi)有定義 DEBUG 宏, 那么 LOG(s) 就會(huì)直接使用 NULL 替換 #ifdef DEBUG#define LOG(s) printf("%s : %d : %s \n", __FILE__, __LINE__, s) #else#define LOG(s) NULL #endif//控制不同的產(chǎn)品編譯 //如果定義了 PRODUCT_1, 那么編譯上面的 fun(), 刪除下面的 fun() //如果沒(méi)有定義 PRODUCT_1, 那么刪除上面的 fun(), 編譯下面的 fun() #ifdef PRODUCT_1 void fun() {LOG("product 1 fun start");printf("product 1 fun() \n");LOG("product 1 fun end"); } #else void fun() {LOG("product 2 fun start");printf("product 2 fun() \n");LOG("product 2 fun end"); } #endifint main() {//控制日志打印LOG("main() start");//根據(jù)當(dāng)前定義的產(chǎn)品打印不同的結(jié)果#ifdef PRODUCT_1printf("product 1 welcom\n");#elseprintf("product 2 welcom\n");#endiffun();LOG("main() end");return 0; }
  • 2.編譯產(chǎn)品1代碼開(kāi)發(fā)版本(debug)并執(zhí)行 : 產(chǎn)品1 的 debug 版本需要定義 DEBUG宏 和 PRODUCT_1 宏, 使用命令 gcc -DDEBUG -DPRODUCT_1 test_1.c 進(jìn)行編譯即可 ;
  • 3.編譯產(chǎn)品2代碼開(kāi)發(fā)版本(bebug)并執(zhí)行 : 產(chǎn)品2 debug 版本, 不需要定義 PRODUCT_1 宏, 但是需要定義 DEBUG 宏, 使用命令 gcc -DDEBUG test_1.c 進(jìn)行編譯即可;
  • 4.編譯產(chǎn)品1代碼發(fā)布版本(release)并執(zhí)行 : 產(chǎn)品1的release 版本, 不定義 DEBUG 宏, 但是需要定義 PRODUCT_1 宏, 使用命令 gcc -DPRODUCT_1 test_1.c 即可 ;
  • 5.編譯產(chǎn)品2代碼發(fā)布版本(release)并執(zhí)行 : 產(chǎn)品2的release版本, 只需要不定義 DEBUG宏 和 PRODUCT_1宏即可, 使用 gcc test_1.c 命令 ;






四. 編譯指示字 ( #error | #line )




1. #error 編譯指示字


(1) #error 簡(jiǎn)介


#error簡(jiǎn)介 :

  • 1.#error 作用 : #error 編譯指示字 用于生成 編譯錯(cuò)誤信息, 立即終止編譯 ; 這個(gè)編譯錯(cuò)誤是程序員自定義的編譯錯(cuò)誤信息;
  • 2.#error 用法 : #error error_message, 這個(gè) error_message 是字符串, 不需要使用 “” 包起來(lái);


#warning 也是編譯指示字, 用于在編譯時(shí)生成警告信息, 但是編譯的過(guò)程不會(huì)終止, 會(huì)繼續(xù)編譯下去 ;




(2) #error #warning 代碼示例


#error #warning 代碼示例 :

  • 1.代碼 :
#include <stdio.h>int main() {//如果沒(méi)有定義 MAX 宏, 預(yù)編譯過(guò)程中就會(huì)終止編譯過(guò)程, 不會(huì)生成 test_1.i 文件;#ifndef MAX#wraning 沒(méi)有定義MAX宏,即將退出!#error 沒(méi)有定義MAX宏,已退出!#endifprintf("程序執(zhí)行完畢!\n");return 0; }
  • 2.編譯結(jié)果( 命令行中定義指定的宏 ) : 使用 gcc -DMAX test_1.c 命令編譯, 此處定義了 MAX 宏, 編譯執(zhí)行成功.
  • 3.編譯結(jié)果( 命令行中不定義指定的宏 ) : 使用 gcc test_1.c 命令編譯, 此處沒(méi)有命定義 MAX 宏, 編譯時(shí)報(bào)錯(cuò).
  • 4.單步操作預(yù)編譯結(jié)果 (定義宏) : 使用 gcc -DMAX -E test_1.c -o test_1.i 命令, 進(jìn)行預(yù)編譯, 結(jié)果預(yù)編譯成功, 查看預(yù)編譯生成的 test_1.i 文件 ;

  • 5.單步操作預(yù)編譯結(jié)果 (不定義宏) : 使用 gcc -E test_1.c -o test_1.i 命令, 進(jìn)行預(yù)編譯, 結(jié)果預(yù)編譯也停止了, 沒(méi)有生成 test_1.i 文件, 因此#error 和 #warning 是在預(yù)編譯階段進(jìn)行處理的 ;




2. #line 編譯指示字


(1) #line 簡(jiǎn)介


#line 簡(jiǎn)介 :

  • 1.#line 作用 : 用于修改當(dāng)前的 __LINE__ 和 __FILE__ 的內(nèi)置宏 ;
  • 2.#line 用法 : #line 行號(hào) 文件名 , 即將當(dāng)前的 內(nèi)置宏 __LINE__ 設(shè)置為 行號(hào), __FILE__ 設(shè)置為 文件名 ;
  • 3.使用環(huán)境 : 調(diào)試代碼時(shí), 編譯 查錯(cuò) 的時(shí)候, 設(shè)置自己關(guān)心的代碼, 這是很古老的調(diào)試方法, 該技術(shù)已經(jīng)被淘汰 ;


(2) #line 代碼示例


#line 使用代碼示例 :

  • 1.代碼示例 :
#include <stdio.h>int main() {//使用 #line 設(shè)置 行號(hào) 和 文件名#line 100 "test_1_han.c"printf("行號(hào) : %d , 文件名 : %s \n", __LINE__ , __FILE__);return 0; }
  • 2.執(zhí)行結(jié)果 :




3. #pragma 編譯器指示字


(1) #pragma 簡(jiǎn)介


#pragma 編譯器指示字 簡(jiǎn)介 :

  • 1.#pragma 作用 : 該 編譯器指示字 指示編譯器完成一些特定的操作 ;
  • 2.編譯器特有, 不可移植 : #pragma 的很多指示字 參數(shù), 這些參數(shù) 都是編譯器 特有的, 編譯器指示字 在 編譯器之間不通用, 不可移植 ;
  • 3.忽略不識(shí)別的指令 : 如果編譯器不支持某個(gè) #pragma 指令 參數(shù), 預(yù)處理器會(huì)忽略這條指令, 并將其刪除;
  • 4.相同指令 操作不同 : 每個(gè)編譯器對(duì) #pragma 指令定義都不同, 可能存在兩個(gè)相同的 #pragma 指令在不同的編譯器中執(zhí)行不同的操作 ;

#pragma 用法 : #pragma 參數(shù)




(2) #pragma message 參數(shù)


#pragma message 參數(shù) :

  • 1.作用 : 編譯器編譯時(shí)將編譯信息輸出到窗口中 ;
  • 2.與 #error 編譯器指示字對(duì)比 : #error只是在出現(xiàn)錯(cuò)誤的時(shí)候, 將錯(cuò)誤信息輸出出來(lái), #pragma message 是單純的額將所有信息輸出出來(lái), 不管對(duì)錯(cuò) ;
  • 3.代碼示例 :
#include <stdio.h>//如果宏定義中定義了 MAX 宏, 那么輸出信息 編譯 MAX #if defined(MAX)#pragma message("編譯 MAX")#define VERSION "MAX"//如果宏定義中定義了 MIN 宏, 那么輸出信息 編譯 MIN #elif defined(MIN)#pragma message("編譯 MIN")#define VERSION "MIN"//如果既沒(méi)有定義 MAX 也沒(méi)有定義 MIN, 那么直接報(bào)錯(cuò)停止編譯 #else#error 需要定義VERSION宏! #endifint main() {printf("%s\n", VERSION);return 0; }
  • 4.不定義宏進(jìn)行編譯 : 如果既不定義 MAX 宏, 也不定義 MIN 宏, 那么直接執(zhí)行 #error 報(bào)錯(cuò);
  • 5.定義 MAX 宏并執(zhí)行 :


(3) #pragma pack 參數(shù)


內(nèi)存對(duì)齊 簡(jiǎn)介 :

  • 1.內(nèi)存對(duì)齊概念 : 不同類型的數(shù)據(jù)存放在內(nèi)存中, 但是其存放順序不是順序存放, 而是按照內(nèi)存對(duì)齊規(guī)則進(jìn)行排列 ;
  • 2.內(nèi)存對(duì)齊原因 : ① 出于性能考慮 , CPU 讀取內(nèi)存不是想讀取多少就讀取多少, 其內(nèi)存讀取是分塊讀取的, 塊大小只能是 2 的n次方字節(jié), 如 1, 2, 4, 8, 16, 字節(jié), 如果被讀取的數(shù)據(jù)沒(méi)有對(duì)齊, 那么需要多次讀取, 這樣性能就降低了 ; ② 硬件平臺(tái)限制 : 可能存在某些硬件只能讀取偶數(shù)地址, 一旦讀取奇數(shù)地址, 直接出現(xiàn)硬件異常導(dǎo)致程序掛掉;
  • 3.內(nèi)存對(duì)齊設(shè)置不能高于編譯器默認(rèn)對(duì)齊字節(jié)數(shù) : GCC 編譯器默認(rèn)支持 4 字節(jié)對(duì)齊, 那么使用 #pragma pack() 只能設(shè)置 1字節(jié) 或 2 字節(jié), 4 字節(jié)支持, 不能設(shè)置其它支持方式; 如果編譯器默認(rèn)是 8 字節(jié), 那么只能設(shè)置 1, 2, 4, 8 四種字節(jié)對(duì)齊方式, 只能往低設(shè)置, 不能高于編譯器默認(rèn)的對(duì)齊字節(jié)數(shù);


結(jié)構(gòu)體 struct 占用內(nèi)存計(jì)算方式 :

  • 1.第一個(gè)起始位置 : 第一個(gè)元素 在 第一個(gè)位置, 從 偏移量 0 開(kāi)始;
  • 2.對(duì)齊參數(shù) : 對(duì)齊數(shù) 和 類型大小 之間較小的一個(gè)是 對(duì)齊參數(shù) ; 這里要注意 如果有結(jié)構(gòu)體元素, 那么該結(jié)構(gòu)體元素的對(duì)齊參數(shù)就是 結(jié)構(gòu)體中的最大對(duì)齊參數(shù);
  • 3.從第二個(gè)開(kāi)始的起始位置 : 除第一個(gè)之外的起始位置, 都必須整除對(duì)應(yīng)的 對(duì)齊參數(shù) ;
  • 4.最終計(jì)算大小要整除所有的對(duì)齊參數(shù) ;
  • 5.注意結(jié)構(gòu)體中有結(jié)構(gòu)體元素 : 結(jié)構(gòu)體中的結(jié)構(gòu)體元素對(duì)齊數(shù)是結(jié)構(gòu)體元素中的最大對(duì)齊數(shù) ;
  • 5.代碼示例 :
#include <stdio.h>//結(jié)構(gòu)體中元素計(jì)算總共有 3 字節(jié), 但是其事實(shí)上占 4 字節(jié) //但是 CPU 讀取內(nèi)存時(shí)一次性讀取 2 的n次方個(gè)字節(jié) 1, 2, 4, 8, 16 字節(jié) //①性能考慮 : 如果3字節(jié)的話 需要先讀取1字節(jié), 再讀取2字節(jié), //這樣就得讀取兩次, 因此比較消耗性能, 索性將其分配4字節(jié),CPU 可以一次讀取 //②硬件平臺(tái)限制 : 硬件平臺(tái)可能只支持讀取偶地址, 如果讀取到了奇數(shù)地址, 直接報(bào)硬件異常 struct struct_1 {//占 1 字節(jié)char c;//占 2 字節(jié)short s; };/* 內(nèi)存對(duì)齊分析 : 1. char c, 對(duì)齊參數(shù)是 char 大小1 和 對(duì)齊數(shù) 2 中的較小值 為 1, 第一個(gè)起始位置沒(méi)有要求 , 起始位置是0, 大小占 1字節(jié), 之后的起始位置 1; 2. short s, 對(duì)齊參數(shù)是 short 大小2 和 對(duì)齊數(shù) 2 中的較小值 為 2, 起始位置要整除 對(duì)齊參數(shù)2,起始位置 2, 占 2 字節(jié), 之后的起始位置 4; 3. char c2, 對(duì)齊參數(shù)是 char 大小1 和 對(duì)齊數(shù) 2 中的較小值 為 1, 起始位置要整除 對(duì)齊參數(shù)1,起始位置 4, 占 1 字節(jié), 之后的起始位置 5; 4. int i, 對(duì)齊參數(shù)是 int 大小4 和 對(duì)齊數(shù) 2 中的較小值 為 2, 起始位置要整除 對(duì)齊參數(shù)2,起始位置 6, 占 4 字節(jié), 之后的起始位置 10; 計(jì)算大小 為 6 + 4 = 10; 最后要求 : 最終的大小必須是整除所有的對(duì)齊參數(shù), 即 1 和 2, 大小 10滿足要求 ; 最終的計(jì)算大小為 10; */ #pragma pack(2) struct struct_2 {char c;short s;char c2;int i; }; #pragma pack()/* 內(nèi)存對(duì)齊分析 : 1. char c, 對(duì)齊參數(shù)是 char 大小1 和 對(duì)齊數(shù) 4 中的較小值 為 1, 第一個(gè)起始位置沒(méi)有要求 , 起始位置 0, 占 1 字節(jié), 之后的起始位置 1; 2. char c2, 對(duì)齊參數(shù)是 char 大小1 和 對(duì)齊數(shù) 4 中的較小值 為 1, 起始位置要整除 對(duì)齊參數(shù)1,起始位置 1, 占 1 字節(jié), 之后的起始位置 2; 3. short s, 對(duì)齊參數(shù)是 short 大小2 和 對(duì)齊數(shù) 4 中的較小值 為 2, 起始位置要整除 對(duì)齊參數(shù)2,起始位置 2, 占 2 字節(jié), 之后的起始位置 4; 4. int i, 對(duì)齊參數(shù)是 int 大小4 和 對(duì)齊數(shù) 4 中的較小值 為 4, 起始位置要整除 對(duì)齊參數(shù)4,起始位置 4, 占 4 字節(jié), 之后的起始位置 8; 計(jì)算大小 為 4 + 4 = 8; 最后要求 : 最終的大小必須是整除所有的對(duì)齊參數(shù), 即 1 ,2, 和 4, 大小 8 滿足要求 ; 最終的計(jì)算大小為 8; */ #pragma pack(4) struct struct_3 {char c;char c2; short s;int i;}; #pragma pack()/* 內(nèi)存對(duì)齊分析 : 這里注意與上面不同的是, 這里出現(xiàn)了一個(gè) struct struct_3 類型, 結(jié)構(gòu)體對(duì)齊參數(shù) : 這里要注意結(jié)構(gòu)體元素的對(duì)齊參數(shù)是該結(jié)構(gòu)體元素中所有對(duì)齊參數(shù)的最大的一個(gè), 不是結(jié)構(gòu)體的大小; 1. char c, 對(duì)齊參數(shù)是 char 大小1 和 對(duì)齊數(shù) 4 中的較小值 為 1, 第一個(gè)起始位置沒(méi)有要求 , 起始位置 0, 占 1 字節(jié), 之后的起始位置 1; 2. char c2, 對(duì)齊參數(shù)是 char 大小1 和 對(duì)齊數(shù) 4 中的較小值 為 1, 起始位置要整除 對(duì)齊參數(shù)1,起始位置 1, 占 1 字節(jié), 之后的起始位置 2; 3. struct struct_3 s, 對(duì)齊參數(shù)是 struct_3中 所有元素最大對(duì)齊數(shù) 4 和 對(duì)齊數(shù) 4 中的較小值 為 4, 起始位置要整除 對(duì)齊參數(shù)4,起始位置 4, 占 8 字節(jié), 之后的起始位置 12; 4. int i, 對(duì)齊參數(shù)是 int 大小4 和 對(duì)齊數(shù) 4 中的較小值 為 4, 起始位置要整除 對(duì)齊參數(shù)4,起始位置 12, 占 4 字節(jié), 之后的起始位置 16; 計(jì)算大小 為 12 + 4 = 16; 最后要求 : 最終的大小必須是整除所有的對(duì)齊參數(shù), 即 1 和 4, 大小 16 滿足要求 ; 最終的計(jì)算大小為 16;*/ #pragma pack(4) struct struct_4 {char c;char c2; struct struct_3 s;int i;}; #pragma pack()/* struct struct_5 和 struct struct_4 結(jié)構(gòu)體定義一樣, 只是一個(gè)是 4 字節(jié)對(duì)齊, 一個(gè)是 8 字節(jié)對(duì)齊 gcc 默認(rèn) 4 字節(jié)對(duì)齊, 其只支持 1, 2, 4 字節(jié)對(duì)齊, 不支持超過(guò) 4 的字節(jié)對(duì)齊 VC++ 默認(rèn) 8字節(jié)對(duì)齊, 其可以支持 1, 2, 4, 8 字節(jié)對(duì)齊, 超過(guò) 8 的字節(jié)對(duì)齊 也不支持; 因此struct_5 雖然定義了 8 字節(jié)對(duì)齊, 但是編譯器不支持, 即又默認(rèn)成4 字節(jié)對(duì)齊, 這里 struct struct_5 和 struct struct_4 大小相同; */ #pragma pack(8) struct struct_5 {char c;char c2; struct struct_3 s;int i;}; #pragma pack()int main() {printf("%ld, %ld, %ld, %ld\n", sizeof(struct struct_2), sizeof(struct struct_3), sizeof(struct struct_4), sizeof(struct struct_5));return 0; }
  • 6.執(zhí)行結(jié)果 :






五. #運(yùn)算符




1. #運(yùn)算符



#運(yùn)算符作用 :

  • 1.將宏參數(shù)轉(zhuǎn)為字符串 : # 運(yùn)算符 可以在 編譯 的 預(yù)編譯 階段, 將宏定義中的參數(shù), 轉(zhuǎn)化為 字符串 ;
  • 2.預(yù)處理器開(kāi)始符號(hào) : 預(yù)處理器 開(kāi)始處理 的符號(hào) ;
  • 3.#運(yùn)算符代碼示例 :
#include <stdio.h>//作用 1 : 預(yù)處理器開(kāi)始符號(hào) //作用 2 : 將宏定義中的參數(shù), 轉(zhuǎn)換為字符串 #define CONVERS_STRING(str) #str//實(shí)例 : 打印出調(diào)用的函數(shù)名稱 和 結(jié)果 #define CALL(fun, num) ( printf ("函數(shù)名稱 %s \n", #fun), fun(num) )int square(int num) {return num * num; }int main() {printf("%s\n", CONVERS_STRING(Hello));printf("%s\n", CONVERS_STRING(666));printf("%s\n", CONVERS_STRING(main));printf("調(diào)用函數(shù) : %d\n", CALL(square, 9));return 0; }
  • 4.預(yù)編譯結(jié)果 : 使用 “gcc -E test_1.c -o test_1.i” 指令進(jìn)行預(yù)編譯, 可以看到 # 運(yùn)算符將 宏定義參數(shù)轉(zhuǎn)為字符串 ;

# 運(yùn)算符 將 Hello 666 main 轉(zhuǎn)為 “Hello” “666” “main” 字符串, 將 square 轉(zhuǎn)為了 “square” 字符串 ;

  • 5.編譯執(zhí)行最終結(jié)果 :




2. ##運(yùn)算符



## 運(yùn)算符作用 :

  • 1.作用 : 在預(yù)編譯階段粘連兩個(gè)符號(hào) ;
  • 2.代碼示例 :
#include <stdio.h>//## 運(yùn)算符 作用 : 預(yù)編譯過(guò)程中 將兩個(gè)符號(hào)連接在一起, 通常用于批量定義變量, 生成不同的變量名稱 // 如定義 int 類型的 student1, student2 ... student9, 九個(gè)變量; #define STUDENT(num) student_variable_##num//定義結(jié)構(gòu)體, 定義變量時(shí)時(shí)需要使用 struct student s1, 很麻煩 //簡(jiǎn)化結(jié)構(gòu)體定義方案 1 : 使用 typedef struct _struct_name_ {} struct_name; 之后就可以使用 struct_name s1, 這樣使用簡(jiǎn)便 typedef struct _student_struct_1_ {int age;int height; }student_struct_1;//簡(jiǎn)化結(jié)構(gòu)體定義方案 2 : 定義下面的宏之后, 可以使用 #define STRUCT(type) typedef struct _tag_##type type;\ struct _tag_##type STRUCT(student_struct_2) {int age;int height; };int main() {//1. 定義變量示例int STUDENT(1) = 1;int STUDENT(2) = 2;printf("%d, %d\n", STUDENT(1), STUDENT(2));//2. 定義結(jié)構(gòu)體常用方法student_struct_1 s1;s1.age = 18;s1.height = 175;printf("%d, %d\n", s1.age, s1.height);//3. 使用 帶 ## 運(yùn)算符 的 宏定義 定義結(jié)構(gòu)體方法student_struct_2 s2;s2.age = 19;s2.height = 155;printf("%d, %d\n", s2.age, s2.height);return 0; }
  • 3.預(yù)編譯結(jié)果 : 使用 “gcc -E test_1.c -o test_1.i” 命令, 執(zhí)行預(yù)編譯 ;
  • 4.最終編譯執(zhí)行結(jié)果 :

總結(jié)

以上是生活随笔為你收集整理的【C 语言】编译过程 分析 ( 预处理 | 编译 | 汇编 | 链接 | 宏定义 | 条件编译 | 编译器指示字 )的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 欧洲精品久久一区二区 | 日韩久久免费视频 | 六月丁香婷婷激情 | 亚洲h| 天堂资源地址在线 | 成人av不卡 | 色一情一区二区三区 | 欧洲美女与动交zozzo | 国产老女人乱淫免费可以 | av一道本| 日韩高清在线观看一区 | 鸥美一级片 | 在线免费看91 | 蜜臀精品一区二区三区 | 国产精品美女久久久久av超清 | 国产精品99视频 | 二区三区av | 性欧美大战久久久久久久久 | 亚洲黄色录像片 | 国产成人免费看一级大黄 | 热久久精 | 久久99视频精品 | 91猎奇在线观看 | 男女啪啪毛片 | 亚洲 欧美 综合 | 欧美精品免费一区二区 | 宅男在线视频 | 五月天中文字幕在线 | a成人在线 | 久艹在线观看 | 欧美精品一区二区三区在线播放 | 国产一区二区av | 国产视频一区二区视频 | 午夜高潮视频 | av男人的天堂在线 | 黑人狂躁日本妞hd | 黄视频免费观看 | 老司机在线观看视频 | 欧美日本在线播放 | 日本一区二区不卡在线观看 | 欧美v日韩 | 欧美福利在线视频 | 怡红院成永久免费人全部视频 | 免费一级做a爰片久久毛片潮 | 欧美成人精品在线 | 人妻体内射精一区二区 | 天堂国产精品 | 欧美一区二区日韩一区二区 | avtt在线播放 | 亚洲乱色熟女一区二区 | 日韩一区免费视频 | av日韩国产 | 男女啊啊啊 | 在线观看成人动漫 | 日韩图色| 激情五月激情 | 欧美精品一二三四 | 亚洲天堂久久久久 | 性视频欧美 | 97精品在线观看 | 毛片网站免费在线观看 | 中文字幕一区二区三区免费看 | 国产三级全黄 | 狠狠干2021 | 老司机午夜在线 | 日本一区二区三区在线免费观看 | 黄色福利视频 | 日韩欧美国产一区二区在线观看 | 日韩精品久 | 欧美日韩国产成人在线 | 91精品国产综合久久香蕉 | 亚洲第一视频在线观看 | 天天看夜夜 | 伊人春色在线视频 | 久久午夜无码鲁丝片午夜精品 | 成人免费看片载 | 亚洲视频在线观看 | 黄网站免费在线 | 国产绿帽一区二区三区 | www.久久av | 欧美亚洲国产精品 | 少妇性l交大片免潘金莲 | 91口爆一区二区三区在线 | 可以免费看的黄色 | 国产欧美第一页 | 欧美干 | 中文字幕日日夜夜 | 岛国精品一区二区 | 亚洲天堂资源在线 | 香蕉视频在线看 | 男女爽爽爽 | 91视频麻豆 | 欧美精品日韩在线观看 | 国产噜噜噜噜久久久久久久久 | 亚色视频在线观看 | 夜夜欢视频 | 色91精品久久久久久久久 | 都市豪门艳霸淫美妇 | 日吊视频 |