天天看點

C語言深度解剖筆記4之預處理

#define

#define用來定義宏,也可以用來定義字元串,尤其是路徑,如C), #define ENG_PATH_3 E:\English\listen_to_this\listen\

有的系統裡規定路徑的要用雙反斜杠“\\”,用define 宏定義表達式

#undef:#undef 是用來撤銷宏定義的 用法:

#define PI 3.141592654

// code

#undef PI

//下面的代碼就不能用PI 了,它已經被撤銷了宏定義。

看下面的題目:

#define X 3

#define Y X*2

#undef X

#define X 2

int z=Y;求z,答案是4,因為X被撤銷了宏定義,而Y沒有,又X被重新定義,是以Y=2*2.

條件編譯:條件編譯的功能使得我們可以按不同的條件去編譯不同的程式部分,因而産生不同的目标代碼檔案。這對于程式的移植和調試是很有用的,下面介紹三種形式:

1.第一種形式:

#ifdef 辨別符

程式段1

#else

程式段2

#endif,本格式中的#else 可以沒有。

第二種形式:

#ifndef 辨別符

程式段1

#else

程式段2

#endif

第三種形式:

#if 常量表達式

程式段1

#else

程式段2

#endif

此外還有一個#elif

檔案包含:檔案包含是預處理的一個重要功能,它可用來把多個源檔案連接配接成一個源檔案進行編

譯,結果将生成一個目标檔案,C語言提供#include 指令來實作檔案包含的操作

#include<filename>和#include"filename",第二個雙引号需要注意的是預處理應在目前目錄中查找檔案名為

filename 的檔案,若沒有找到,則按系統指定的路徑資訊,搜尋其他目錄。

#error 預處理指令的作用是,編譯程式時,隻要遇到#error 就會生成一個編譯錯誤提

示消息,并停止編譯。其文法格式為:

#error error-message

注意,宏串error-message 不用雙引号包圍。遇到#error 指令時,錯誤資訊被顯示,如

#ifdef XX

...

#error XX has been defined

#else

#endif

#pragma 預處理:它的作用是設定編譯器的狀态或者是訓示編譯器完成一些特定的動作

其格式一般為:#pragma para,其中para 為參數,下面來看一些常用的參數

#pragma message("消息文本"),當編譯器遇到這條指令時就在編譯輸出視窗中将消息文本列印出來。

假設我們希望判斷自己有沒有在源代碼的什麼地方定義了_X86 這個宏可以用下面的方法

#ifdef _X86

#Pragma message(“_X86 macro activated!”)

#endif

如果我們定義了_X86,應用程式在"編譯時"就會在編譯輸出視窗裡顯示“_X86 macro activated!”

#pragma once,這條指令能夠保證頭檔案被編譯一次

]#pragma hdrstop表示預編譯頭檔案到此為止,後面的頭檔案不進行預編譯

#pragma warning:最常見的用法及時#pragma warning(disable:4996)

#pragma pack:這是一個與記憶體對齊有關的預處理。

使用指令#pragma pack (n),編譯器将按照n 個位元組對齊。

使用指令#pragma pack (),編譯器将取消自定義位元組對齊方式。

接下來我們來仔細讨論下記憶體對齊。

記憶體對齊的原因

1.硬體平台問題:某些硬體平台隻能在某些位址處取某些特定類型的資料。否則會抛出異常

2.性能問題:資料結構(尤其是棧)應該盡可能地在自然邊界上對齊為了通路未對齊的記憶體,處理器需要作兩次記憶體通路;然而,對齊的記憶體通路僅需要一次通路。

記憶體對齊問題,主要存在于struct和union等複合結構在記憶體中的分布

struct A{
    char a;
    int b;
    short c;
};
 
struct B{
    short c;
    char a;
    int b;
};
           

兩個結構體中的成員是一樣的,隻是分布的先後順序不同,我們可以試一下輸出sizeof(A)和sizeof(B),得到的結構是不一樣的,前者是12,後者是8.,這就是因為記憶體對齊導緻的結果不同。

如果想得知結構體的某個特定成員在結構體的位置,則使用offsetof宏(定義于頭檔案stddef.h)。

記憶體對齊規則:

對于結構中的第一個成員位于偏移量(offsetof)為0的位址位置,

以後每個資料成員的對齊按照#pragma pack指定的數值和這個資料成員自身長度中,比較小的那個進行。即min($pragma pack,資料成員類型的大小),下面我們把這個叫做實際對齊數。

最後整體的結構體或者聯合體也要對齊,按照#pragma pack指定的數值和結構(或聯合)最大資料成員長度中,比較小的那個進行。 min(#pragram pack() , 長度最長的資料成員);

當#pragma pack的n值等于或超過所有資料成員長度的時候,這個n值的大小将不産生任何效果。

Windows中預設對齊數為8,Linux中預設對齊數為4;vc++6.0預設是8

我們來分析下上面的代碼。

struct A{
    char a;
    int b;
    short c;
};
           

char a是一個位元組。min(sizeof(char),8)=1,是以第一個記憶體塊是0,接着是b,b的偏移量是1,int是4個位元組,min(sizeof(int),8)=4,1(偏移量)不是4的整數倍,是以,應該把b的偏移量應該為4,即在a的後面再添加3個位元組,是以b所占的記憶體塊是4-8,接着是short c,short是2個位元組。min(sizeof(short),8)=2,c的偏移量是8,8是2的整數倍。是以不用改變。此時一共占了10個位元組,結構體的對齊, min(8,4)=4,是以總體的大小應該是4的倍數,是以還需要添加兩個位元組。

如圖

C語言深度解剖筆記4之預處理
struct B{
    short c;
    char a;
    int b;
};
           

這是第二個結構體,與第一個的結構體是資料成員順序的不同,short是2個位元組,是以c的記憶體塊是0-1,接着是a,那麼a的偏移量是2,而min(8,1)=1,2是1的整數倍數,是以a的記憶體塊是2,接着是b,min(8,4)=4,b的偏移量是3,3不是4的整數倍數,是以需要擴充到4,是以需要在a的後面再添加一個位元組(這個位元組編号是3)是以b的記憶體塊是(4-7),接着是結構體的對齊,min(8,4)=4,而整體的大小是8,8是4的倍數,是以不需要添加了。

如圖:

C語言深度解剖筆記4之預處理
cpu在讀取記憶體時是一塊一塊讀取的,這就是上面說的記憶體塊。

# 這也是一個運算符。它可以把語言符号轉化為字元串。使用方法是“#+需要替換的語言符号”舉個例子

#define SQR(x) printf("The square of x is %d.\n", ((x)*(x)));

SQR(8);//輸出為:The square of x is 64

#define SQR(x) printf("The square of "#x" is %d.\n", ((x)*(x)));

SQR(8);//輸出的是:The square of 8 is 64.

##:這個運算符把兩個語言符号組合成單個語言符号,就是個粘合劑,将前後兩部分粘合起來

#define XNAME(n) x ## n

XNAME(8)//輸出是x8。

c++ c