前置處理器應用篇
你所不知道的 C 語言:前置處理器應用篇
邁向專業程式設計必備技能 Copyright (慣C) 2016 宅色夫
概況
- 回顧 C99/C11 的 macro 特徵,探討 C11 新的關鍵字 _Generic 搭配 macro 來達到 C++ template 的作用
- 探討 C 語言程式的物件導向程式設計、抽象資料型態 (ADT) / 泛型程式設計 (Generics)、程式碼產生器、模仿其他程式語言,以及用 preprocessor 搭配多種工具程式的技巧
不要小看 preprocessor
「怎樣讓你的程式師朋友懷疑人生:把他代碼裡的分號換成希臘文的問號」
用希臘問號取代分號
; http://unicode-table.com/en/037E/ Greek Question Mark
; http://unicode-table.com/en/003B/ Semicolon
把以下加進 Makefile 的 CFLAGS 中:
-D’;’=’;’
會看到以下悲劇訊息:
<command-line>:0:1: error: macro names must be identifiers
C++,叫我如何接納你?
source: dlib
純 C 還是最美!
開發物件導向程式時,善用 preprocessor 可大幅簡化開發
延續「物件導向程式設計篇」的思考,我們可善用 preprocessor,讓程式碼更精簡、更容易維護,從而提昇程式設計思考的層面
以 raytracing 程式為例 [ source ],考慮以下 macro (objects.h):
#define DECLARE_OBJECT(name) \
struct __##name##_node; \
typedef struct __##name##_node *name##_node; \
struct __##name##_node { \
name element; \
name##_node next; \
}; \
void append_##name(const name *X, name##_node *list); \
void delete_##name##_list(name##_node *list);
DECLARE_OBJECT(light)
DECLARE_OBJECT(rectangular)
DECLARE_OBJECT(sphere)
light 在 DECLARE_OBJECT(light)
中會取代 name,因此會產生以下程式碼:
struct __light_node;
typedef struct __light_node *light_node;
struct __light_node { light element; light_node next; };
void append_light(const light *X, light_node *list);
void delete_light_list(light_node *list);
** generate (產生) / (生成) 可用 gcc -E -P 觀察輸出:
- [ ] _POSIX_SOURCE
A Simple Object System -> The Prototype Object System
延伸閱讀: 圖解 JavaScript 的物件模型
typedef enum { NORTH, SOUTH, EAST, WEST} Direction;
typedef struct {
char *description;
int (*init)(void *self);
void (*describe)(void *self);
void (*destroy)(void *self);
void *(*move)(void *self, Direction direction);
int (*attack)(void *self, int damage);
} Object;
int Object_init(void *self);
void Object_destroy(void *self);
void Object_describe(void *self);
void *Object_move(void *self, Direction direction);
int Object_attack(void *self, int damage);
void *Object_new(size_t size, Object proto, char *description);
#define NEW(T, N) Object_new(sizeof(T), T##Proto, N)
#define _(N) proto.N
_Generic [C11]
C11 沒有 C++ template,但有以 macro 為基礎的 type-generic functions,主要透過 _Generic 關鍵字。
- [ ] C11 standard (ISO/IEC 9899:2011): 6.5.1.1 Generic selection (page: 78-79)
以下求開立方根 (cube root) 的程式 (generic.c) 展示了 _Generic 的使用:
#include
#include
#define cbrt(X) \
_Generic((X), \
long double: cbrtl, \
default: cbrt, \
const float: cbrtf, \
float: cbrtf \
)(X)
int main(void)
{
double x = 8.0;
const float y = 3.375;
printf("cbrt(8.0) = %f\n", cbrt(x));
printf("cbrtf(3.375) = %f\n", cbrt(y));
}
編譯並執行:
$ gcc -std=c11 -o generic generic.c -lm
$ ./generic
cbrt(8.0) = 2.000000
cbrtf(3.375) = 1.500000
\
double cbrt(double x);
float cbrtf(float x);
- x (型態為 double) => cbrt(x) => selects the default cbrt
- y (型態為 const float) => cbrt(y) =>
- gcc: converts const float to float, then selects cbrtf
- clang: selects cbrtf for const float
注意 casting 的成本和正確性議題
C11 程式碼:
#include
void funci(int x) { printf("func value = %d\n", x); }
void funcc(char c) { printf("func char = %c\n", c); }
void funcdef(double v) { printf("Def func's value = %lf\n", v); }
#define func(X) \
_Generic((X), \
int: funci, char: funcc, default: funcdef \
)(X)
int main() {
func(1);
func('a');
func(1.3);
return 0;
}
輸出結果是
func value = 1
func value = 97
Def func's value = 1.300000
相似的 C++ 程式碼:
template
void func(T v) { cout << "Def func's value = " << v << endl; }
template <> void func(int x) { printf("func value = %d\n", x); }
template <>
void func(char c) { printf("func char = %c\n", c); }
對照輸出,對於 character 比對不一致,會轉型為 int 。
func value = 1
func char = a
Def func's value = 1.3
[tgmath.h](http://pubs.opengroup.org/onlinepubs/009695399/basedefs/tgmath.h.html) - type-generic macros
延伸閱讀:
- C11 - Generic Selections
- Fun with C11 generic selection expression
- Experimenting with _Generic() for parametric constness in C11
應用: Unit Test
Unity: Unit Test for C source: http://www.throwtheswitch.org/unity/ File: unity/unity_fixture.h
Google Test source: https://github.com/google/googletest File: mock/gmock-generated-actions.h
應用: Object Model
ObjectC: use as a superset of the C language adding a lot of modern concepts missing in C
Files
C: exception jmp=> setjmp + longjmp
應用: Exception Handling
ExtendedC library extends the C programming language through complex macros and other tricks that increase productivity, safety, and code reuse without needing to use a higher-level language such as C++, Objective-C, or D.
File: include/exception.h
應用: ADT
pearldb: A Lightweight Durable HTTP Key-Value Pair Database in C
內含 Klib: a Generic Library in C