gcc 展開前置處理的技巧

fcamel
fcamel
Sep 5, 2018 · 6 min read

編譯 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 可以得知更多用法。

fcamel的程式開發心得

Notes about software development.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade