C語言預處理命令

2020-08-09 14:17:02
  • 首先三種形式的命令:宏定義,檔案包含,條件編譯命令

  1. 宏定義主要是:#define,#undef

#define PI 3.1415926                   /*不帶參數的宏定義*/
#define Max(a,b)  a>b?a:b              /*帶參數的宏定義*/

下面 下麪舉例說明定義宏FAILED用於檢測數據的正確性。

①#define的應用:

#define FAILED(Status) ((Status)<0)
#include "stdio.h"
void main()
{
    int d;
    printf ("Please input a integer number(n>0)\n");
    do{
        scanf("%d" ,&d);
    }while(FAILED(d));
} 


其中while(FAILED(d))在編譯之前被無條件替換爲while(d<0)。
宏定義和呼叫在形式與函數比較相似,但是原理是不同。

②#undef的應用:

#include "stdio.h"
void Test();
int main(int argc, char* argv[])
{
    #define CONST_NAME1 "CONST_NAME1"
    printf("%s\n",CONST_NAME1);
    #undef CONST_NAME1  
    printf("%s\n",CONST_NAME1); /*錯誤,CONST_NAME1的定義已經取消*/
    {
        #define CONST_NAME2 "CONST_NAME2"
        printf("%s\n",CONST_NAME2);
    }
    printf("%s\n",CONST_NAME2);
    return 0;
}
void Test()
{
    printf("%s\n",CONST_NAME2);
}

在程式的編譯的時候,系統提示如下資訊
error C2065: 'CONST_NAME1' : undeclared identifier
原因是宏CONST_NAME1被#undef取消了,所以在編譯printf("%s\n",CONST_NAME1)找不到CONST_NAME1的定義

2、檔案包含:

 #include <檔名> ,這種屬於標準方式,用於編譯系統指定的檔案。

 #include 「檔名」,這種屬於使用者方式,查詢使用者當前工作的資料夾中的檔案,如果不存在則再按照標準方式查詢。

3、條件編譯(常見的三種形式):

  ①第一種形式:

#if defined(或者是ifdef)   <識別符號>

        <程式段1>

[#else

        <程式段2>]

#endif

②第二種形式:

#if !defined(或者是ifndef)    <識別符號>

        <程式段1>

[#else

        <程式段2>]

#endif

③第三種形式常用與C++編譯器中。

#ifdef …

[#elif … ]

[#elif …]

#else …

 #endif

#if指令
#if指令檢測跟在製造另關鍵字後的常數表達式。如果表達式爲真,則編譯後面的程式碼,知道出現#else、#elif或#endif爲止;否則就不編譯。

#endif指令
#endif用於終止#if預處理指令。

#else指令
#else指令用於某個#if指令之後,當前面的#if指令的條件不爲真時,就編譯#else後面的程式碼。#endif指令將中指上面的條件塊。

4、其他條件編譯命令:

 #error:

語法格式如下:
#error token-sequence
其主要的作用是在編譯的時候輸出編譯錯誤資訊token-sequence,從方便程式設計師檢查程式中出現的錯誤。例如下面 下麪的程式

#include "stdio.h"
int main(int argc, char* argv[])
{
#define CONST_NAME1 "CONST_NAME1"
  printf("%s\n",CONST_NAME1);
#undef CONST_NAME1
#ifndef CONST_NAME1
#error No defined Constant Symbol CONST_NAME1
#endif
{
#define CONST_NAME2 "CONST_NAME2"
   printf("%s\n",CONST_NAME2);
}
   printf("%s\n",CONST_NAME2);
   return 0;
}

在編譯的時候輸出如編譯資訊
fatal error C1189: #error : No defined Constant Symbol CONST_NAME1

#pragma

#pragma是一個C語言中的預處理指令,它的作用是設定編譯器的狀態或者是指示編譯器完成一些特定的動作。依據定義,編譯指示是機器或操作系統專有的,且對於每個編譯器都是不同的。

其格式一般爲: #pragma Para

其中Para 爲參數,下面 下麪來看一些常用的參數。

  1. message 參數。

Message 參數是我最喜歡的一個參數,它能夠在編譯資訊輸出視窗中輸出相應的資訊,這對於原始碼資訊的控制是非常重要的。其使用方法爲:

#Pragma message(「訊息文字」)

當編譯器遇到這條指令時就在編譯輸出視窗中將訊息文字列印出來。當我們在程式中定義了許多宏來控制原始碼版本的時候,我們自己有可能都會忘記有沒有正確的設定這些宏,此時我們可以用這條指令在編譯的時候就進行檢查。假設我們希望判斷自己有沒有在原始碼的什麼地方定義了_X86這個宏可以用下面 下麪的方法

#ifdef _X86

#Pragma message(「_X86 macro activated!」)

#endif

當我們定義了_X86這個宏以後,應用程式在編譯時就會在編譯輸出視窗裏顯示「_

X86 macro activated!」。我們就不會因爲不記得自己定義的一些特定的宏而抓耳撓腮了

  1. 另一個使用得比較多的pragma參數是code_seg。格式如:

#pragma code_seg( ["section-name"[,"section-class"] ] )

它能夠設定程式中函數程式碼存放的程式碼段,當我們開發驅動程式的時候就會使用到它。

  1. #pragma once (比較常用)

只要在標頭檔案的最開始加入這條指令就能夠保證標頭檔案被編譯一次,這條指令實際上在VC6中就已經有了,但是考慮到相容性並沒有太多的使用它。

  1. #pragma hdrstop表示預編譯標頭檔案到此爲止,後面的標頭檔案不進行預編譯。BCB可以預編譯標頭檔案以加快鏈接的速度,但如果所有標頭檔案都進行預編譯又可能佔太多磁碟空間,所以使用這個選項排除一些標頭檔案。

有時單元之間有依賴關係,比如單元A依賴單元B,所以單元B要先於單元A編譯。你可以用#pragma startup指定編譯優先順序,如果使用了#pragma package(smart_init) ,BCB就會根據優先順序的大小先後 先後編譯。

  1. #pragma resource "*.dfm"表示把*.dfm檔案中的資源加入工程。*.dfm中包括表單

外觀的定義。

  1. #pragma warning( disable : 4507 34; once : 4385; error : 164 )

等價於:

#pragma warning(disable:4507 34) // 不顯示4507和34號警告資訊

#pragma warning(once:4385) // 4385號警告資訊僅報告一次

#pragma warning(error:164) // 把164號警告資訊作爲一個錯誤。

同時這個pragma warning 也支援如下格式:

#pragma warning( push [ ,n ] )

#pragma warning( pop )

這裏n代表一個警告等級(1---4)。

#pragma warning( push )儲存所有警告資訊的現有的警告狀態。

#pragma warning( push, n)儲存所有警告資訊的現有的警告狀態,並且把全域性警告

等級設定爲n。

#pragma warning( pop )向棧中彈出最後一個警告資訊,在入棧和出棧之間所作的

一切改動取消。例如:

#pragma warning( push )

#pragma warning( disable : 4705 )

#pragma warning( disable : 4706 )

#pragma warning( disable : 4707 )

//.......

#pragma warning( pop )

在這段程式碼的最後,重新儲存所有的警告資訊(包括4705,4706和4707)。

  1. pragma comment(...)

該指令將一個註釋記錄放入一個物件檔案或可執行檔案中。

常用的lib關鍵字,可以幫我們連入一個庫檔案。

  1. progma pack(n)

指定結構體對齊方式!#pragma pack(n)來設定變數以n位元組對齊方式。n位元組對齊就是說變數存放的起始地址的偏移量有兩種情況:第一、如果n大於等於該變數所佔用的位元組數,那麼偏 移量必須滿足預設的對齊方式,第二、如果n小於該變數的型別所佔用的位元組數,那麼偏移量爲n的倍數,不用滿足預設的對齊方式。結構的總大小也有個約束條 件,分下面 下麪兩種情況:如果n大於所有成員變數型別所佔用的位元組數,那麼結構的總大小必須爲佔用空間最大的變數佔用的空間數的倍數;

  否則必須爲n的倍數。下面 下麪舉例說明其用法。

#pragma pack(push) //儲存對齊狀態

#pragma pack(4)//設定爲4位元組對齊

struct test

{

char m1;

double m4;

int m3;

};

#pragma pack(pop)//恢復對齊狀態

爲測試該功能,可以使用sizeof()測試結構體的長度!

③#line

此命令主要是爲強制編譯器按指定的行號,開始對源程式的程式碼重新編號,在偵錯的時候,可以按此規定輸出錯誤程式碼的準確位置。
形式1
語法格式如下:
# line constant 「filename」:其作用是使得其後的原始碼從指定的行號constant重新開始編號,並將當前檔案的名命名爲filename。例如下面 下麪的程式如下:
#include "stdio.h"
void Test();
#line 10 "Hello.c"
int main(int argc, char* argv[])
{
#define CONST_NAME1 "CONST_NAME1"
printf("%s\n",CONST_NAME1);
#undef CONST_NAME1
printf("%s\n",CONST_NAME1);
{
#define CONST_NAME2 "CONST_NAME2"
printf("%s\n",CONST_NAME2);
}
printf("%s\n",CONST_NAME2);
return 0;
}
void Test()
{
printf("%s\n",CONST_NAME2);
}
提示如下的編譯資訊:Hello.c(15) : error C2065: 'CONST_NAME1' : undeclared identifier
表示當前檔案的名稱被認爲是Hello.c, #line 10 "Hello.c"所在的行被認爲是第10行,因此提示第15行出錯。
形式2語法格式如下:

# line constant:其作用在於編譯的時候,準確輸出出錯程式碼所在的位置(行號),而在源程式中並不出現行號,從而方便程式設計師準確定位。

④運算子###
在ANSI C中爲預編譯指令定義了兩個運算子——#和##。
# 的作用是實現文字替換,例如
#define HI(x) printf("Hi,"#x"\n");
void main()
{
    HI(John);
}
程式的執行結果:Hi,John
在預編譯處理的時候, "#x"的作用是將x替換爲所代表的字元序列。在本程式中x爲John,所以構建新串「Hi,John」。
##的作用是串連線。例如
#define CONNECT(x,y) x##y
void main()
{
int a1,a2,a3;
CONNECT(a,1)=0;
CONNECT(a,2)=12;
a3=4;
printf("a1=%d\ta2=%d\ta3=%d",a1,a2,a3);
}
程式的執行結果爲:a1=0 a2=12 a3=4
在編譯之前, CONNECT(a,1)被翻譯爲a1, CONNECT(a,2)被翻譯爲a2。

⑤預定義常數
__LINE__:當前原始碼的行號,爲整型常數

__FILE__:當前編譯程式檔案的名稱,爲字串

__DATE__:編譯程式檔案日期,爲字串(」MM DD YYYY"形式,如」 Jan 19 1993」)

__TIME__:編譯程式檔案時間,爲字串("hh:mm:ss"形式,如」08:30:23」)

__STDC__:ANSI C標誌,整型常數1,說明此程式相容ANSI C標準。