C之 #pragma(二十二)
預處理期將忽略它不認識的 #pragma 指令,不同的編譯器可能以不同的方式解釋同一條 #pragma 指令。一般用法:#pragma parameter。 註意:不同的 parameter 參數語法和意義各不相同!
#pragma message:a> message 參數在大多數的編譯器中都有相似的實現;b> message 參數在編譯時輸出消息到編譯輸出窗口中;c> message 用於條件編譯中可提示代碼的版本信息。它與 #error
下來我們來分析個示例代碼,代碼如下
#include <stdio.h> #if defined(ANDROID20) #pragma message("Compile Android SDK 2.0...") #define VERSION "Android 2.0" #elif defined(ANDROID23) #pragma message("Compile Android SDK 2.3...") #define VERSION "Android 2.3" #elif defined(ANDROID40) #pragma message("Compile Android SDK 4.0...") #define VERSION "Android 4.0" #else #error Compile Version is not provided! #endif int main() { printf("%s\n", VERSION); return 0; }
這段代碼是想利用 #pragma message 定義一條輸出信息,我們來看看在 gcc 編譯器中輸出什麽
我們可以看出第一次沒有定義 ANDROID 參數,編譯直接報我們提示的錯誤。那麽它在輸出信息的時候,也同樣將 #pragma message 輸出了。我們再在 BCC 編譯器中編譯下,看看輸出是什麽
那麽在 BCC 編譯器中我們將參數寫在後面它還不識別,它編譯之後的結果是沒有 #pragma message,直接顯示後面的信息。由此我們可以看到在不同的編譯器中,對 message 的處理結果不一樣。
下來我們再講講 #pragma once
test.c
#include <stdio.h> #include "global.h" #include "global.h" int main() { printf("g_value = %d\n", g_value); return 0; }
global.h
#pragma once int g_value = 1;
我們在 gcc 編譯器中編譯下,看看結果如何
我們看到在 gcc 中沒報錯誤,直接運行。我們下來在 BCC 中再來編譯下
我們看到在 BCC 中直接報錯,顯然在 BCC 編譯器中就不支持這種寫法。
那麽下來我們再來看看 C 語言中的內存對齊,什麽是內存對齊呢?不同類型的數據在內存中按照一定規則排列,但是不一定是按照順序的一個接一個的排列。那麽為什麽需要內存對齊呢?原因有這麽幾點:1、CPU 對內存的讀取不是連續的,而是分成塊讀取的,塊的大小只能是1、2、4、8、16 ... 字節;2、當讀取操作的數據未對齊,則需要兩次總線周期來訪問內存,因此性能會大打折扣;3、某些硬件平臺只能從規定的相對地址處讀取特定類型的數據,否則會產生異常。#pragma pack 用於指定內存對齊方式。
下面我們來看個示例代碼,代碼如下
#include <stdio.h> struct Test1 { char c1; short s; char c2; int i; }; struct Test2 { char c1; char c2; short s; int i; }; int main() { printf("sizeof(Test1) = %d\n", sizeof(struct Test1)); printf("sizeof(Test2) = %d\n", sizeof(struct Test2)); return 0; }
我們看到兩個結構體中的成員變量都一樣,但是位置不同。那麽問題來了,它們所占的內存大小相同嗎?我們來看看編譯結果
很明顯結果不一樣,那麽為什麽呢?這就是內存對齊了,在計算機內部,默認的是4字節對齊,因為這樣效率最高。我們可以指定它們都是1字節對齊,這樣結果就一樣了。#pragma pack 能夠改變編譯器的默認對齊方式,下面給個示例
#pragma pack(1) struct Test1 { char c1; short s; char c2; int i; }; #pragma pack()
分別在兩個結構的前後都這樣設置按照 1 字節對齊的方式,我們再來看看結果如何
這下它們占的內存大小就一樣了。之前的那種內存分布如下圖所示
那麽 struct 占用的內存大小是怎樣計算的呢?第一個成員起始於 0 偏移處,每個成員按其類型大小和 pack 參數中較小的一個進行對齊;偏移地址必須能被對齊參數整除,結構體成員的大小取其內部長度最大的數據成員作為其大小;結構體總長度必須為所有對齊參數的整數倍。編譯器在默認情況下按照 4 字節對齊。
下來我們來分析下之前的程序中結構體默認的按照 4 字節怎樣對齊的
#pragma pack(4) struct Test1 { // 內存對齊 起始距離 內存大小 char c1; // 1 0 1 short s; // 2 2 2 char c2; // 1 4 1 int i; // 4 8 4 }; #pragma pack() #pragma pack(4) struct Test2 { // 內存對齊 起始距離 內存大小 char c1; // 1 0 1 char c2; // 1 1 1 short s; // 2 2 2 int i; // 4 4 4 }; #pragma pack()
那麽大家和上面的表對照下,是不是一樣呢。下來我們再做個實驗,按照8字節對齊試試,代碼如下
#include <stdio.h> #pragma pack(8) struct S1 { // 內存對齊 起始距離 內存大小 short a; // 2 0 2 long b; // 4 4 4 }; struct S2 { // 內存對齊 起始距離 內存大小 char c; // 1 0 1 struct S1 d;// 4 4 8 double e; // 8 16 8 }; #pragma pack() int main() { printf("%d\n", sizeof(struct S1)); printf("%d\n", sizeof(struct S2)); return 0; }
按照我們的分析,上面應該分別打印出 8 和 24。我們來看看 gcc 編譯器的結果
我們看到打印出的是 8 和 20,那麽我們是不是分析出錯了?別著急哈,再看看 BCC 編譯器的結果
我們看到 BCC 編譯器和我們分析的一致。那麽 gcc 為什麽會打印 20 呢?原來在 gcc 編譯器中不支持 8 字節對齊的方式,因此它是按照 4 字節對齊方式進行打印的。那麽最後一個 double 的起始距離就是 12 了,因此結果就是 20 啦。
通過對 #pragma 的學習,總結如下:1、#pragma 用於指示編譯器完成一些特定的動作;2、它所定義的很多指示字是編譯器特有的;3、#pragma message 用於 自定義編譯消息、#pragma once 用於保證頭文件只被編譯一次、#pragma pack 用於指示內存對齊方式。
歡迎大家一起來學習 C 語言,可以加我QQ:243343083。
C之 #pragma(二十二)