C 預處理指令

0. Overview

C的預處理指令格式為#name,均以#開頭,#和指令名之間不可有空白字符,#前可以有空字符,但為增強可讀性,一般應從第一列開始

#name不能由宏展開得來,name也不能由宏展開得來,如

// Wrong 1
#define INC #include
INC <stdio.h>
// Wrong 2
#define INC include
#INC <stdio.h>

預處理指令只能占一行,但是在寫代碼時可以用'\'分隔多行,但處理時仍會將這多行合為一行。有些指令帶參數,參數需與指令由空白字符分隔

預處理指令主要提供下列功能:

  • 引入頭文件
  • 宏展開
  • 條件編譯
  • line control:#line(感覺一般人用不著)
  • 診斷(diagnostics):可在編譯器檢查程序,發出errors或warnings

1. 頭文件

#include來包含頭文件,該指令的參數形式有兩種:

  1. #include <file>
    用于系統頭文件。Preprocessor將在a standard list of system directories下搜尋文件file,可以用編譯器的-I選項來將目錄添加到這個list
  2. #include "file"
    用于程序自身的頭文件。Preprocessor的搜尋順序如下: a. 先在包含該文件的當前目錄搜尋文件file, b. 然后在quote directories中搜尋,可以用編譯器的-iquote選項來將目錄添加到quote directories中 c. 最后再在用于搜尋<file>的目錄下搜尋(即 1. 中的順序)(所以用#include "stdio.h"只要你不覆蓋這個頭文件的話也不會出現問題,總能找著)

#include的參數無論是用""還是<>括起來,都如同一個字符串,里面的注釋不會被識別,宏也不會展開。但是不同于字符串的是,backslash不再有轉義作用,而是一個單純的字符'\'

在這一行,文件名參數后面除了注釋外不能有任何其他內容

只包含一次 Once-Only Headers

如果一個頭文件被include兩次,編譯器就會處理兩次,因此可能會出錯,如重定義等等,標準做法是用所謂的wrapper #ifndef將頭文件的內容包起來,如:

/* File foo.  */
#ifndef FILE_FOO_SEEN
#define FILE_FOO_SEENthe entire file#endif /* !FILE_FOO_SEEN */

代碼片段中的宏FILE_FOO_SEEN叫做controlling macro或者guard macro,在用戶程序頭文件中,該宏的名字不能_開頭,在系統頭文件中,該宏的名字需要__(雙下劃線)開頭以免與用戶程序頭文件沖突。在任意類型的頭文件中,該宏的名字應該包含頭文件文件名再加上額外的文字以避免與其他頭文件沖突

2. 宏 Macros

宏是賦予名字的一段代碼,每次使用時都將名字替換成宏內容。宏分為兩種,它們在使用時有很大的不同:

  1. Object-like macros:使用時像用data objects一樣
  2. Function-like macros:使用時像函數調用一樣

2.1 對象形式的宏 Object-like macros

Object-like macro就是一個簡單的標志符,表示一個代碼片段,在使用時由這個代碼片段來替換,用法:

#define NAME macro_body

宏body又叫expansion或replacement list,是一個token序列

按照慣例,宏的名字一般用大寫字母

#define macro_body也只占一行,并且macro_body后面不能有其他內容(除空白字符或注釋外),在寫代碼時也可以用'\'分隔多行,但預處理時仍會將它們合為一行

C preprocessor順序地掃描源程序,因此宏定義只從定義處開始生效

宏展開是遞歸進行的,preprocessor將一個宏展開后會接著處理展開后的結果,如果這里面有其他的宏,會繼續展開下去。但是如果結果里面再次出現剛剛展開的這個宏的話將不會展開第二次,以免出現無限遞歸的情況

#define TABLESIZE BUFSIZE
#define BUFSIZE 1024
TABLESIZE
// -> 先展開為 BUFSIZE
// -> 再展開為 1024

注意雖然宏會展開多次,但是每次的展開過程只是單純地用body替換name,如上面的例子中在展開TABLESIZE時只是單純地用BUFSIZE來替換它,接下來preprocessor才檢查替換結果是不是另一個宏

2.2 函數形式的宏 Function-like macros

如其名,這種宏使用起來像函數調用一樣。用法:

#define name() body

注意,小括號()必須和宏的名字連在一起,否則會被當成object-like宏來展開,同時,在使用時也必須用name()的形式(此時name()間可以有空格,2.3中同),只用name的話不會被展開

2.3 宏參數

Function-like宏像函數一樣可以接受參數,用法:

#define name(params_list)

其中params_list是參數列表,參數必須是有效的C標志符,由,分隔(參數列表中可以出現空格,但是空格沒有實際作用)

在“調用”函數形式的宏時,將實參列表寫在宏name后面的小括號里,由,分隔,函數形式宏的“調用”不限制在一行內,可以寫成多行,但是參數數量必須和定義時的數量相匹配。可以實參可以是空,但是數量也必須匹配(直白講即逗號數量必須一致),如:

min(, b)        → ((   ) < (b) ? (   ) : (b))
min(a, )        → ((a  ) < ( ) ? (a  ) : ( ))
min(,)          → ((   ) < ( ) ? (   ) : ( ))
min((,),)       → (((,)) < ( ) ? ((,)) : ( ))min()      error→ macro "min" requires 2 arguments, but only 1 given
min(,,)    error→ macro "min" passed 3 arguments, but takes just 2

#define min(X, Y)  ((X) < (Y) ? (X) : (Y))x = min(a, b);          →  x = ((a) < (b) ? (a) : (b));y = min(1, 2);          →  y = ((1) < (2) ? (1) : (2));z = min(a + 28, *p);    →  z = ((a + 28) < (*p) ? (a + 28) : (*p));

在展開時會去除各個實參的leading、trailing whitespace,實參的token序列中的whitespace會減成一個空格。在每個實參中,小括號必須平衡,小括號中的逗號不會結束這個參數(即小括號中的逗號不是實參分隔符),但中括號和大括號不要求平衡,而且它們中的逗號會作為實參分隔符截斷這個參數

宏定義中若參數出現在字符串中,在展開時不會展開成相應實參,如:

#define foo(x) x, "x"
foo(bar)        → bar, "x"

2.4 字符串化 Stringizing

有時可能需要講宏參數轉換成字符串常量,但是在 2.3 的最后提到字符串中的參數不會被實參替換,為了解決這個問題,可以用預處理操作符#來進行轉換。當參數有一個前導#時,preprocessor會將其替換為實參,再轉換成字符串常量,但是這個過程發生后,被轉換成的字符串中如果還有宏則不會繼續展開,如果還想繼續展開,則需要寫成多級宏的形式,如:

#define xstr(s) str(s)
#define str(s) #s
#define foo 4
str (foo)→ "foo"
xstr (foo)→ xstr (4)→ str (4)→ "4"

2.5 拼接

預處理操作符##用于在宏body中將兩個tokens拼在一起,如A ## B將展開為AB,要求展開后必須是一個有效的C標志符,如一個標志符和數字拼接,兩個數字間的拼接,一些復合操作符如+=的拼接等等,有些拼接是無效的,如x+

拼接常見的應用場景為宏參數間的拼接,如:

#define COMMAND(NAME)  { #NAME, NAME ## _command }struct command commands[] =
{COMMAND (quit),COMMAND (help),…
};

2.6 取消宏定義

#undef name用于取消宏定義,name可以是object-like宏的名字,或者是function-like宏的名字(不用加小括號以及參數列表)

3. 條件編譯

3.1 條件編譯常用場景

  • 根據機器架構或操作系統的不同使用不同的代碼
  • 將原文件編譯成兩個不同的程序,其中一個版本可能會用于輸出一些data進行debugging等等
  • 使用#if 0來將排除一段代碼,但將其保留在源文件中用作注釋

3.2 條件編譯語法

ifdefifndef

#ifdef MACROcontrolled text#endif /* MACRO */

if

#if expressioncontrolled text#endif /* expression */

expression是一個integer類型的C表達式,可以包含

  • 整形常量
  • 字符常量
  • 數學運算表達式和邏輯運算表達式(遵循短路求值)
  • 宏,在計算宏所代表的表達式前將先展開所有的宏
  • defined預處理指令
  • 所有不是宏的標志符都視為數字0,函數形式的宏但沒有調用實參列表也視為0

defined

用在#if#elif表達式中,用于測試一個名字是否被定義成了一個宏,defined namedefiend ( name )作用相同:如果name定義為了一個宏,則表達式值為1,否則為0,因此#if defined MACRO等價于#ifdef MACRO

在測試多個宏是否存在時defined比較有用,如:

#if defined (__vax__) || defined (__ns16000__)

else

可以用在#if#ifdef#ifndef

elif

elif不需要一個#endif和其匹配

4. 診斷信息

  • #error導致preprocessor產生一個fatal error,#error所在行的剩余tokens組成錯誤信息
  • #warning導致preprocessor產生一個warning并繼續預處理,#warning所在行的剩余tokens組成錯誤信息

兩者都不對其參數進行宏展開

參考

  • GNU - The C Preprocessor: Macros

轉載于:https://www.cnblogs.com/jerrywossion/p/11071192.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/386588.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/386588.shtml
英文地址,請注明出處:http://en.pswp.cn/news/386588.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Windows NAT端口映射

Windows本身命令行支持配置端口映射&#xff0c;條件是已經安裝了IPV6&#xff0c;啟不啟用都無所謂&#xff0c;我在win7和server2008上是可以的。xp&#xff0c;2003裝了ipv6協議也是可以的。 CMD下操作 增加端口映射&#xff0c;將10.10.10.10的8080映射到10.10.10.11的80…

阿里P8大牛親自教你!史上最全的Android面試題集錦,這原因我服了

一、架構師專題 想要掌握復雜的技術&#xff0c;必須要理解其原理和架構。本模塊結合實際一線互聯網大型項目理解架構思維&#xff0c;抽絲剝繭&#xff0c;層層深入&#xff0c;幫助大家成為Android架構師&#xff0c;在思想上對架構認識有一次升華&#xff0c;并知其所以然&a…

面向對象程序設計——UML分析和本學期總結

? 隨著第四單元UML第二次作業的結束&#xff0c;本學期的OO學習也宣告結束了&#xff08;但還得寫博客&#xff09;&#xff0c;下面就對本單元和本次作業做一個總結。 第四單元兩次作業的架構設計 ? 本單元是對UML的結構進行解析&#xff0c;第一次作業是對UML類圖的解析&am…

docker linux k8s kubeadm

一. 安裝docker 1.添加yum國內依賴 yum -y install yum-utils yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo2.安裝docker yum -y install docker-ce docker-ce-cli containerd.io3.啟動docker systemctl start docker4…

小程序FMP優化實錄,大廠面試題匯總

前言 金九銀十面試季&#xff0c;相信大家肯定急需一套Android面試寶典&#xff0c;今天小編就給大家準備了我珍藏已久的Android高階面試寶典&#xff0c;一份超級詳細的Android面試必備知識點&#xff0c;供大家學習 &#xff01; 想必每一個安卓程序員都有追求大廠的決心&a…

文件CRC和MD5校驗

文件CRC和MD5校驗 CRC和MD5用于文件和數據的傳輸校驗&#xff0c;以確認是否接收成功。 unit CRCMD5;interface { 獲取文件CRC校驗碼 } function GetFileCRC(const iFileName: string): String; { 獲取字符串CRC校驗碼 } function GetStringCRC(const Str: string): Cardinal; …

Oracle字符分隔函數(split)

為了讓 PL/SQL 函數返回數據的多個行&#xff0c;必須通過返回一個 REF CURSOR 或一個數據集合來完成。REF CURSOR 的這種情況局限于可以從查詢中選擇的數據&#xff0c;而整個集合在可以返回前&#xff0c;必須進行具體化。Oracle 9i 通過引入的管道化表函數糾正了后一種情況。…

已成功拿下字節、騰訊、脈脈offer,吐血整理

為什么想跳槽&#xff1f; 簡單說一下當時的狀況&#xff0c;我在這家公司做了兩年多&#xff0c;這兩年多完成了一個大項目&#xff0c;作為開發的核心主力&#xff0c;開發壓力很大&#xff0c;特別是項目上線前的幾個月是非常辛苦&#xff0c;幾乎每晚都要加班到12點以后&a…

復雜HTML解析

#再端一碗BeautifulSoup #獲取《戰爭與和平》中的人物名字from urllib.request import urlopen from bs4 import BeautifulSouphtml urlopen("http://www.pythonscraping.com/pages/warandpeace.html") bsObj BeautifulSoup(html,html.parser)#namelist bsObj.fin…

java main方法里調用mapper

在main方法中調用mybatis的mapper&#xff0c;一次性執行導入數據功能package com.runxsoft.test;import com.runxsoft.iutils.common.utils.UserUtils; import com.runxsoft.superwe.base.SqlVo; import com.runxsoft.superwe.base.mapper.ProtogenesisMapper; import com.run…

已成功拿下字節、騰訊、脈脈offer,滿滿干貨指導

開頭 籠統來說&#xff0c;中年程序員容易被淘汰的原因其實不外乎三點。 1、輸出能力已到頂點。這個人奮斗十來年了&#xff0c;依舊碌碌無為&#xff0c;很明顯這人的天花板就這樣了&#xff0c;說白了&#xff0c;天賦就這樣。 2、適應能力越來越差。年紀大&#xff0c;有家…

ServletRequest HttpServletRequest 請求方法 獲取請求參數 請求轉發 請求包含 請求轉發與重定向區別 獲取請求頭字段...

原文地址:ServletRequest HttpServletRequest 請求方法 獲取請求參數 請求轉發 請求包含 請求轉發與重定向區別 獲取請求頭字段ServletRequest 基本概念 JavaWeb中的 "Request"對象 實際為 HttpServletRequest 或者 ServletRequest, 兩者都為接口服務器接收請求…

c#掃描圖片去黑邊(掃描儀去黑邊)

/// <summary> /// 自動去除圖像掃描黑邊 /// </summary> /// <param name"fileName"></param> public static void AutoCutBlackEdge(string fileName) { //打開圖像 Bit…

已成功拿下字節、騰訊、脈脈offer,算法太TM重要了

一、背景介紹 從實用角度梳理一篇能夠幫大家快速掃盲的CMake基礎教程&#xff0c;也是對我目前負責項目的一次學習總結。既然選擇從項目實用性考慮&#xff0c;下面的講解內容可能并不一定完整&#xff0c;更多的是符合項目目前使用到的一些特性。 接下來正面回答這個問題&am…

SpringBoot2.0 Actuator 監控參數說明

主要內容更 監控參數說明 Maven坐標 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency><groupId>io.micrometer</groupId>&…

帶你一步一步深入Handler源碼,醍醐灌頂!

開頭 最近有粉絲反應&#xff0c;不想做安卓了&#xff0c;有朋友轉到前端了&#xff0c;安卓不行了&#xff0c;問我怎么辦&#xff1f; 自從RN&#xff0c;Weex這種跨平臺編程語言出來以后&#xff0c;安卓將死的言論總是不絕于耳。隨著頗有摧枯拉朽之勢Flutter的出現&…

Spring基于狀態機squirrel-foundation簡單使用

squirrel-foundation的一些使用方法在百度上資料還是比較少&#xff0c;我是根據以下三個大佬寫的文章借鑒的&#xff0c;在這里記錄一下。 1、squirrel-foundation-demo 2、Squirrel使用&#xff08;中文文檔&#xff09; 3、squirrel-foundation狀態機的使用細節 我在這里直接…

記得把每一次面試當做經驗積累,深夜思考

開頭 Android開發&#xff0c;假如開始沒有任何的開發經驗的話&#xff0c; 千萬不要著急&#xff0c;不要想著在短時間內就把一個語言學習好&#xff0c; 因為你之前沒有任何的學習經驗&#xff0c; 在這個過程中需要有耐心地學習完JAVA的基礎知識&#xff0c; 然后才開始踏上…

squirrel-foundation-demo

一個簡單的squirrel-foundation-demo 利用狀態機模擬一個訂單的支付過程。 squirrel-foundation沒有任何嚴重的依賴關系&#xff0c;因此基本上它應該是高度可嵌入的。squirrel-foundation沒有整合spring框架&#xff0c;所以首先要用spring集成squirrel-foundation。spring集成…

MongoDB學習目錄

MongoDB基礎篇 MongoDB 之 $ 關鍵字 python操作MongoDB 轉載于:https://www.cnblogs.com/yanzhi-1996/p/11095016.html