C之 #pragma(二十二)

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

        預處理期將忽略它不認識的 #pragma 指令,不一樣的編譯器可能以不一樣的方式解釋同一條 #pragma 指令。通常用法:#pragma parameter注意:不一樣的 parameter 參數語法和意義各不相同!性能

        #pragma message:a> message 參數在大多數的編譯器中都有類似的實現;b> message 參數在編譯時輸出消息到編譯輸出窗口中;c> message 用於條件編譯中可提示代碼的版本信息。它與 #error #warning 不一樣,#pragma message 僅僅表明一條編譯消息,不表明程序錯誤。學習

        下來咱們來分析個示例代碼,代碼以下spa

#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 編譯器中輸出什麼圖片.png3d

        咱們能夠看出第一次沒有定義 ANDROID 參數,編譯直接報咱們提示的錯誤。那麼它在輸出信息的時候,也一樣將 #pragma message 輸出了。咱們再在 BCC 編譯器中編譯下,看看輸出是什麼blog

圖片.png

        那麼在 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.hit

#pragma once

int g_value = 1;

        咱們在 gcc 編譯器中編譯下,看看結果如何

圖片.png

        咱們看到在 gcc 中沒報錯誤,直接運行。咱們下來在 BCC 中再來編譯下

圖片.png

        咱們看到在 BCC 中直接報錯,顯然在 BCC 編譯器中就不支持這種寫法。

        那麼下來咱們再來看看 C 語言中的內存對齊,什麼是內存對齊呢?不一樣類型的數據在內存中按照必定規則排列,可是不必定是按照順序的一個接一個的排列。那麼爲何須要內存對齊呢?緣由有這麼幾點:一、CPU 對內存的讀取不是連續的,而是分紅塊讀取的,塊的大小隻能是一、二、四、八、16 ... 字節;二、當讀取操做的數據未對齊,則須要兩次總線週期來訪問內存,所以性能會大打折扣;三、某些硬件平臺只能從規定的相對地址處讀取特定類型的數據,不然會產生異常。#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;
}

        咱們看到兩個結構體中的成員變量都同樣,可是位置不一樣。那麼問題來了,它們所佔的內存大小相同嗎?咱們來看看編譯結果

圖片.png

        很明顯結果不同,那麼爲何呢?這就是內存對齊了,在計算機內部,默認的是4字節對齊,由於這樣效率最高。咱們能夠指定它們都是1字節對齊,這樣結果就同樣了。#pragma pack 可以改變編譯器的默認對齊方式,下面給個示例

#pragma pack(1)
struct Test1
{
    char  c1;
    short s;
    char  c2;
    int   i; 
};
#pragma pack()

        分別在兩個結構的先後都這樣設置按照 1 字節對齊的方式,咱們再來看看結果如何

圖片.png

        這下它們佔的內存大小就同樣了。以前的那種內存分佈以下圖所示

圖片.png

        那麼 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 編譯器的結果

圖片.png

        咱們看到打印出的是 8 和 20,那麼咱們是否是分析出錯了?彆着急哈,再看看 BCC 編譯器的結果

圖片.png

        咱們看到 BCC 編譯器和咱們分析的一致。那麼 gcc 爲何會打印 20 呢?原來在 gcc 編譯器中不支持 8 字節對齊的方式,所以它是按照 4 字節對齊方式進行打印的。那麼最後一個 double 的起始距離就是 12 了,所以結果就是 20 啦。

        經過對 #pragma 的學習,總結以下:一、#pragma 用於指示編譯器完成一些特定的動做;二、它所定義的不少指示字是編譯器特有的;三、#pragma message 用於 自定義編譯消息、#pragma once 用於保證頭文件只被編譯一次、#pragma pack 用於指示內存對齊方式。

        

        歡迎你們一塊兒來學習 C 語言,能夠加我QQ:243343083

相關文章
相關標籤/搜索