前置處理器應用篇

你所不知道的 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 觀察輸出:

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 關鍵字。

以下求開立方根 (cube root) 的程式 (generic.c) 展示了 _Generic 的使用:

#include <stdio.h>
#include <math.h>

#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

<math.h> 的原型宣告:

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 <stdio.h>
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 <typename T>
void func(T v) { cout << "Def func's value = " << v << endl; }
template <> void func<int>(int x) { printf("func value = %d\n", x); }
template <>
void func<char>(char c) { printf("func char = %c\n", c); }

對照輸出,對於 character 比對不一致,會轉型為 int 。

func value = 1
func char = a
Def func's value = 1.3

<tgmath.h> - type-generic macros

libm 的 <tgmath.h> 實做

延伸閱讀:

應用: 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


书籍推荐