1. 程式人生 > >C之 #pragma(二十二)

C之 #pragma(二十二)

C語言 #pragma message #pragma once #pragma pack

我們今天來介紹下 C 語言中的 #pragma#pragma 用於指示編譯器完成一些特定的動作。#pragma 所定義的很多指示字是編譯器特有的,在不同的編譯器間是不可移植的。

預處理期將忽略它不認識的 技術分享圖片#pragma 指令,不同的編譯器可能以不同的方式解釋同一條 #pragma 指令。一般用法:#pragma parameter註意:不同的 parameter 參數語法和意義各不相同!

#pragma message:a> message 參數在大多數的編譯器中都有相似的實現;b> message 參數在編譯時輸出消息到編譯輸出窗口中;c> message 用於條件編譯中可提示代碼的版本信息。它與 #error

#warning 不同,#pragma message 僅僅代表一條編譯消息,不代表程序錯誤。

下來我們來分析個示例代碼,代碼如下

#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

,它是用於保證頭文件只被編譯一次。那麽問題來了,我們之前講講 #ifndef xxx_h_ #define xxx_h_ #endif 這種用法。它們兩個有什麽區別呢?因為後一種的實現是基於宏參數實現的,所以它每次包含到頭文件時便會進去檢查,所以效率不高。但是因為是宏參數,所以這是 C 語言支持的,在每個編譯器中都會識別。#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(二十二)