gcc 展開前置處理的技巧

fcamel
fcamel的程式開發心得
6 min readSep 5, 2018

編譯 C/C++ 程式前,編譯器會用 C 前置處理器 (cpp, C Preprocessor) 展開 #include#define#if … 等指令後的程式碼。gcc -E 作完前置處理就會停下來,不會繼續編譯,並可配合不同參數觀看展開的內容。

保留 define 內容

gcc -E -dM 可以保留 define 內容,方便查詢原始名稱。比方說 Linux 的 system call 的錯誤值會存在 errno,是個數字。要查詢數字對到的意思,需要看 <errno.h> 的內容。

假設程式輸出 errno 的值是 111,可以這麼查:

$ echo "#include <errno.h>" | gcc -E -dM - | grep " 111$"
#define ECONNREFUSED 111

然後 google ECONNREFUSED 或看 system call 的 man page 說明,就知道是怎麼一回事了。

列出 include 的內容

想知道是否有正確 include 到標頭檔,或是想看間接 include 什麼檔,可以用 gcc -M。這參數原本的用途是找出相依的標頭檔,用來寫入 makefile 讓 make 知道。若希望過濾掉系統標頭檔,可以改用 -MM

$ echo "#include <stdio.h>" | gcc -E -M -
-: /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
/usr/include/x86_64-linux-gnu/bits/wordsize.h \
/usr/include/x86_64-linux-gnu/gnu/stubs.h \
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
/usr/lib/gcc/x86_64-linux-gnu/5/include/stddef.h \
/usr/include/x86_64-linux-gnu/bits/types.h \
/usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/x86_64-linux-gnu/5/include/stdarg.h \
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
/usr/include/x86_64-linux-gnu/bits/sys_errlist.h

綜合實例: 找出實作 GetReq() 的原始碼

在觀察 libxext 原始碼 src/XSync.c 的時候,想找出 GetReq()的實作。但在 libxext 的原始碼裡沒看到。所以先查 GetReq()是由那個標頭檔引入的:

$ gcc -E ./src/XSync.c | grep GetReq
extern void *_XGetRequest(Display *dpy, CARD8 type, size_t len);
= (xSyncInitializeReq *) _XGetRequest(dpy, 0, 8)
...

結果沒看到宣告。猜測 GetReq()可能是 #define 的結果,改成從 #define 查:

$ gcc -E -dM ./src/XSync.c | grep GetReq
#define GetReqSized(name,sz,req) req = (x ##name ##Req *) _XGetRequest(dpy, X_ ##name, sz)
#define GetReq(name,req) GetReqSized(name, SIZEOF(x ##name ##Req), req)
...

得知 GetReq()其實是 _XGetRequest()

查看 _XGetRequest() 從那裡引入的:

gcc -E ./src/XSync.c | less
...
# 418 "/usr/include/X11/Xlibint.h" 3 4
extern void *_XGetRequest(Display *dpy, CARD8 type, size_t len);
...

接著查 /usr/include/X11/Xlibint.h 放在那個套件:

# 查詢已安裝套件, 速度較快
$ dpkg --search /usr/include/X11/Xlibint.h
libx11-dev:amd64: /usr/include/X11/Xlibint.h
# 查詢全部套件
$ apt-file search /usr/include/X11/Xlibint.h
libx11-dev: /usr/include/X11/Xlibint.h

取得 libx11 的原始碼,然後找到原始碼的位置:

$ apt-get source libx11-dev

$ grep -RI _XGetRequest .
./src/XlibInt.c:void *_XGetRequest(Display *dpy, CARD8 type, size_t len)

至此大功告成!

附帶一提,若想查看 #define 從那裡引入的,可以用 -dD:

$ gcc -E -dD ./src/XSync.c | egrep "(GetReq|include)"
# 418 "/usr/include/X11/Xlibint.h" 3 4
extern void *_XGetRequest(Display *dpy, CARD8 type, size_t len);
#define GetReqSized(name,sz,req) req = (x ##name ##Req *) _XGetRequest(dpy, X_ ##name, sz)
# 435 "/usr/include/X11/Xlibint.h" 3 4
#define GetReq(name,req) GetReqSized(name, SIZEOF(x ##name ##Req), req)

查看 man gcc 的 -dletters 或 -dCHARS 可以得知更多用法。

--

--