---
# System prepended metadata

title: C 語言：前置處理器應用
tags: [NCKU Linux Kernel Internals, ' C語言']

---

---
tags: NCKU Linux Kernel Internals, C語言
---

# C 語言：前置處理器應用

[你所不知道的 C 語言：前置處理器應用篇](https://hackmd.io/@sysprog/c-preprocessor?type=view)

## 善用 preprocessor
### `#` 
[Stringizing Operator / Stringizing](https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html): 把一個表示式(statement)變成字串
```c=
#include <stdio.h>
#define assertLike(s) if (s) printf(#s " pass\n"); \
                    else printf(#s " failed\n") 

int main()
{
    assertLike(0 == 1);
    assertLike(1 == 1);
    return 0;
}
```
舉例來說，可以用來簡單實作一個檢查 statement 為 True 或 False 的 macro。可以看到在 `assertLike()` 的 if 中 `s` 被確實執行，而透過 `#s` 可以被 `printf` 印出。

使用 stringizing 需注意其特性:
```c=
#include <stdio.h>
#define p(s) printf(#s)
#define STR "TRY IT?"

int main()
{
    p( p = "foo\n");
    printf("\n");
    printf("p = \"foo\\n\"");
    
    p( p = foo                l\n);
    p( p = foo l\n);
    
    // p(,); <- this one will fail!
    p(STR);
    
    p( p = foo /*YOU CANT SEE ME*/)
    return 0;
}
```
* stringizing 會刪除參數前後的空格
* preprocessor 並非只是在 statement 前後加上兩個 `"`，而是會適當的加上跳脫字元(backslash-escapes)，所以上面程式的第 6 行和第 8 行效果相同。
* statement 中的連續空白會被轉成單一空白，所以上面程式的第 10 行和第 11 行等價。
* 如 `,` `)` 的符號不能被 stringizing
* macro argument 不能被 stringizing，因此第 15 行會印出 `STR`，而不是 `TRY IT?`
* 註解會轉成空白，因此程式第 17 行中，不會印出註解中的內容。

### `##` 
[Token Pasting Operator / Concatenation ](https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html) : 連接兩個 "token"。

這有甚麼用呢? 舉例來說，我們可以用來建立一個複雜的資料結構，讓這個結構有統一的命名規則。

```c=
#include <stdio.h>

#define MAKE_STRUCT(name) \
	typedef struct name { \
	int name##_price; \
	} _t_##name

int main()
{
   MAKE_STRUCT(apple);
   _t_apple a;
   a.apple_price = 10;
   printf("%d \n",a.apple_price);
   
   return 0;
}
```

Reference:
* [理解C语言中的Stringizing操作符和Token Pasting操作符](http://wangjieqiang.com/2019/09/01/%E7%90%86%E8%A7%A3C%E8%AF%AD%E8%A8%80%E4%B8%AD%E7%9A%84Stringizing%E6%93%8D%E4%BD%9C%E7%AC%A6%E5%92%8CToken-Pasting%E6%93%8D%E4%BD%9C%E7%AC%A6/)

### `_Generic`
C 語言中沒有 C++ 裡的 `template`，但我們可以透過 `_Generic` 達到接近的效果。

舉例來說，如果我們要實作一個印出兩個數字的函數 `print_pair()`:

```c=
int print_pair_int(int a, int b) {
    printf("%d %d\n",a,b);
}
```

這個函式可以印出兩個 int，如果呼叫 `print_pair_int(1, 3)`，會印出`1 3`。然而，因為輸入是 int 的原因，呼叫 `print_pair_int(1.1, 3.3)` ，只會印出 `1 3`。如果我們希望輸入浮點數會轉為印出浮點數，就必須再寫另一個 function。

```c=
int print_pair_float(double a, double b) {
    printf("%f %f\n",a,b);
}
```

這豈不是很麻煩嗎?明明是相同的功能，只是針對不同的型別就必須呼叫不同的 function。難道不能對於不同的型別，都有一個共同的 function 介面嗎?

於是我們可以藉由使用 `_Generic`，達到目的。

```c=
#include <stdio.h>

#define print_pair(a, b)                \
    _Generic((a),                \
        int: _Generic((b),    \
                    int : print_pair_int,  \
                    double: print_pair_int_double \
                ),               \
        double: _Generic((b),    \
                    int : print_pair_double_int,  \
                    double: print_pair_double \
                )                \
    )(a, b)
    
int print_pair_int(int a, int b) {
    printf("%d %d\n",a,b);
}

int print_pair_double(double a, double b) {
    printf("%f %f\n",a,b);
}

int print_pair_double_int(double a, int b) {
    printf("%f %d\n",a,b);
}

int print_pair_int_double(int a, double b) {
    printf("%d %f\n",a,b);
}

int main()
{
   print_pair(1,3);
   print_pair(1.1,3.3);
   print_pair(1,3.3);
   print_pair(1.1,3);
   return 0;
}
```

## Example: Cloak
[Cloak](https://github.com/pfultz2/Cloak) 專案展示了 C preprocessor 的能耐，讓我們一起來看看這個專案是如何使用前置處理器的各種技巧的。

> Reference: [C Preprocessor tricks, tips, and idioms
](https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms)

### Pattern Matching

首先，讓我們來思考如何用 macro 設計一個 if，可以像下面這樣定義:

```cpp
#define IIF(cond) IIF_ ## cond
#define IIF_0(t, f) f
#define IIF_1(t, f) t
```

`IIF` 根據 `cond`，可以擴展成 `IIF_0` 或 `IIF_1`，再往下擴展成輸入的 `f` 或 `t`。因此，例如 `IIF(1)(true, false)` 這樣的 statement，最後會被處理成 true。而 `IIF(0)(true, false)` 則會被處理成 false。

不過這個設計在 `IIF` 參數是另一個 macro 時會無法得到預期的展開，舉例來說:

```cpp
#define A() 1
IIF(A())(true, false) 
```

在這個用法下，`A()` 不會先被展開成 1，`IIF(A())` 會展開成 `IIF_A()`，成為未被定義的語法。我們可以透過在中間在插入一層轉換來解決此問題。

```cpp
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
```

然後重新定義 `IIF`。

```cpp
#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t
```

來檢視一下現在 `IIF(A())(true, false)` 的展開步驟:
* `IIF(A())` 展開成 `PRIMITIVE_CAT(IIF_, 1)`，最後展開成 `IIF_1` 
* 因此 `IIF_1(true, false)` 展開得到 true

藉此，`IIF` 可以接受另一個最終可以展開成 0 或 1 的 macro 作為參數。但是更符合實際的 if macro 應該要可以區分 0 和 非 0(這也是其之所以命名為 `IIF` 的原因)，後面我們會探討如何再增加一層轉換來解決此問題。

總之，藉由類似的手法，我們也可以建立其他的 macro，作為 macro 中的 operator 來使用:
* 將 0 / 1 反轉的 complement
```cpp
#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0
```
* bit and
```cpp
#define BITAND(x) PRIMITIVE_CAT(BITAND_, x)
#define BITAND_0(y) 0
#define BITAND_1(y) y
```
* increase / decrease
```cpp
#define INC(x) PRIMITIVE_CAT(INC_, x)
#define INC_0 1
#define INC_1 2
#define INC_2 3
#define INC_3 4
#define INC_4 5
#define INC_5 6
#define INC_6 7
#define INC_7 8
#define INC_8 9
#define INC_9 9

#define DEC(x) PRIMITIVE_CAT(DEC_, x)
#define DEC_0 0
#define DEC_1 0
#define DEC_2 1
#define DEC_3 2
#define DEC_4 3
#define DEC_5 4
#define DEC_6 5
#define DEC_7 6
#define DEC_8 7
#define DEC_9 8
```
### Detection

Detection 技巧用來區分輸入參數的類型，展開成 0 或者 1 的結果。讓我們先來檢視該技巧的核心 macro:

```cpp
#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define PROBE(x) x, 1,
```

* 當 `CHECK` 的輸入參數是 `PROBE` 時，舉例來說，`CHECK(PROBE(~))`，會展開成 `CHECK_N(~, 1, 0, )`，因此最後展開成 1
* 但假如是 `CHECK(xxx)`，則會擴展成 `CHECK_N(xxx, 0, )` 因此會變成 0

基於這個技巧，可以建立一些檢查用的 macro。例如以下的 macro 可以用來判斷輸入的參數是否是括號。

```cpp
#define IS_PAREN(x) CHECK(IS_PAREN_PROBE x)
#define IS_PAREN_PROBE(...) PROBE(~)
IS_PAREN(()) // Expands to 1
IS_PAREN(xxx) // Expands to 0
```

* `IS_PAREN(())` 展開成 `CHECK(IS_PAREN_PROBE())` 再展開成 `CHECK(PROBE(~))`，如前所述會變成 1
* 而 `IS_PAREN(xxx)` 會展開成 `CHECK(IS_PAREN_PROBE xxx)` 直接被展開成 `CHECK_N(IS_PAREN_PROBE xxx, 0,)`，因此最後會變成 0

透過 detection 技巧，我們可以更進前面不夠完整的 if，建立更通用的 `IF` macro。

首先，我們需要一個 `NOT` macro，結合 `CHECK` 和 `PROBE` 的技巧，讓 0 成為 1，而非 0 則展開成 0:

```cpp
#define NOT(x) CHECK(PRIMITIVE_CAT(NOT_, x))
#define NOT_0 PROBE(~)
```
然後再定義 `BOOL`，透過 `COMPL` 將 0/1 反轉。如此一來，condition 0 維持為 0，而非 0 皆會轉換成 1，作為 `IIF` 的參數，建立出 `IF`。

```cpp
#define BOOL(x) COMPL(NOT(x))
#define IF(c) IIF(BOOL(c))
```

如果有些混亂的話，可以藉由實際的案例來看看為甚麼這個設計可行:


以 `IF(n)(true, false)` 為例，展開的順序如下:

1. `IF(n)` 
2. -> `IIF(BOOL(n))` 
3. -> `IIF(COMPL(NOT(n)))` 
4. -> `IIF(COMPL(CHECK(NOT_n)))` 
 

當 n = 0 時，NOT_0 會展開成 `PROBE`，導致 `CHECK(NOT_n)` 變成 1，因此繼續往下展開的話:

5. -> `IIF(COMPL(1))`
6. -> `IIF(0)`
7. -> false

反之，當 n != 0，NOT_n 不能再往下展開，於是 `CHECK(NOT_n)` 變成 0，因此繼續往下展開的話:

5. -> `IIF(COMPL(0))`
6. -> `IIF(1)`
7. -> true

於是，現在我們得到一個更泛用的 if macro，可以區別 0 和 非 0 的 condition。

然後就可以在此之上建立一個 `WHEN` macro，當 condition 為 true 時繼續向下展開，否則就停止。

```cpp
#define EAT(...)
#define EXPAND(...) __VA_ARGS__
#define WHEN(c) IF(c)(EXPAND, EAT)
```

### Recursion
:::warning
個人覺得 recursion 部份有點複雜，所以細節部份我還沒辦法很清楚的理解，因此內容僅供參考，建議閱讀原文QQ
:::
一般而言，macro 是不能被遞迴展開的。當 macro 被展開時，會變成 [painted blue](https://en.wikipedia.org/wiki/Painted_blue) 狀態，被標示的 token 不能繼續往下展開。舉例來說，`#define A A()` 在展開 `A` 時並不會變成 `A()()()()...`。

為了透過 macro 實現遞迴的效果。我們需要有技巧的防止 macro 被 painted blue。其次，我們可以透過判斷 macro 是否 painted blue，來決定是否要延伸展開到另一個 macro。

#### Deferred expression
Deferred expression 所指是一段需要被更多的 scan 才能完整展開的 expression。下面是一個例子:

```cpp
#define EMPTY()
#define DEFER(id) id EMPTY()
#define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
#define EXPAND(...) __VA_ARGS__

#define A() 123
A() // Expands to 123
DEFER(A)() // Expands to A () because it requires one more scan to fully expand
EXPAND(DEFER(A)()) // Expands to 123, because the EXPAND macro forces another scan
```

透過 `DEFER` macro，`A()` 不會立即被展開，直到我們再用一層 `EXPAND` 才可以將 `A()` 展開為其定義的 123。

`OBSTRUCT` 則可以 defer 兩次，舉例來說:
```cpp
#define RR() 3
OBSTRUCT(RR)()
```
* `OBSTRUCT(RR)()` 的展開為 `RR EMPTY EMPTY() ()()` 然後再展開為 `RR EMPTY ()()`
* 因此 `EXPAND(OBSTRUCT(RR)())` 展開為 `RR()`
* 因此 `EXPAND(EXPAND(OBSTRUCT(RR)()))` 展開為 `3`


為甚麼需要這個技巧呢?如同一開始所說，當 macro 被掃描然後展開，就會成為 painted blue 狀態，preprocessor 不會將 painted blue 的 token 繼續展開，這也是為甚麼 macro 沒辦法遞迴展開的緣故。為了不讓某個特定的 macro 直接被 painted blue，我們可以透過 deferring  expansion 的技巧讓 macro 需要足夠次數的 scan 才能被展開。


透過 `EVAL` macro 可以達到多次 scan 的目的。如下所示，從 `EVAL` 展開會有3個 `EVAL1`，再展開有 3^2 個 `EVAL2` ，以此類推，展開到 `EVAL5` 時有 3^5 個，這表示最多可以 scan $1+3+3^1+...3^5 = 355$ 次。
```cpp
#define EVAL(...)  EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL2(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL3(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define EVAL4(...) EVAL5(EVAL5(EVAL5(__VA_ARGS__)))
#define EVAL5(...) __VA_ARGS__
```

基於這個技巧，就可以定義 `REPEAT` macro。在這個 macro 中，`REPEAT_INDIRECT` 使得 `REPEAT` 得以遞迴自己。並透過  `OBSTRUCT` defer 兩次(因為 scan 包含 `WHEN` 和 `EVAL` 兩次)。

```cpp
#define REPEAT(count, macro, ...) \
    WHEN(count) \
    ( \
        OBSTRUCT(REPEAT_INDIRECT) () \
        ( \
            DEC(count), macro, __VA_ARGS__ \
        ) \
        OBSTRUCT(macro) \
        ( \
            DEC(count), __VA_ARGS__ \
        ) \
    )
#define REPEAT_INDIRECT() REPEAT

//An example of using this macro
#define M(i, _) i
EVAL(REPEAT(8, M, ~)) // 0 1 2 3 4 5 6 7
```

也可以定義 `WHILE`，會遞迴展開 `op` 直到 `pred` 為 true。
```cpp
#define WHILE(pred, op, ...) \
    IF(pred(__VA_ARGS__)) \
    ( \
        OBSTRUCT(WHILE_INDIRECT) () \
        ( \
            pred, op, op(__VA_ARGS__) \
        ), \
        __VA_ARGS__ \
    )
#define WHILE_INDIRECT() WHILE

// example
#define PRED(state, ...) BOOL(state)
#define OP(state, ...) DEC(state), state, __VA_ARGS__
EVAL(WHILE(PRED, OP, 8,)) // 0, 1, 2, 3, 4, 5, 6, 7, 8,
```

### Comparison
要比較兩個 token 是否相同，我們可以善用 painted blue 的狀態。透過將 macro 做為另一個 macro 的參數，並利用如果兩個 macro 不同，則展開會繼續往下的特性(見以下範例)，則可以實作出比較的效果。

假如我想比較的 token 有 foo 跟 bar，那麼可以如以下定義。

```cpp
#define COMPARE_foo(x) x
#define COMPARE_bar(x) x
```

透過 `PRIMITIVE_COMPARE`
```cpp
#define PRIMITIVE_COMPARE(x, y) IS_PAREN \
( \
COMPARE_ ## x ( COMPARE_ ## y) (())  \
)
```

`PRIMITIVE_COMPARE(foo, bar)`
1. -> `IS_PAREN(COMPARE_foo(COMPARE_bar)(()))`
2. ->  `IS_PAREN(COMPARE_bar(()))`
3. -> `IS_PAREN(())` 
4. 1

`PRIMITIVE_COMPARE(foo, foo)`
1. -> `IS_PAREN(COMPARE_foo(COMPARE_foo)(()))`
2. ->  `IS_PAREN(COMPARE_foo(()))`
3. 0 (因為從 1 到 2 時 `COMPARE_foo(x)` 已經 painted blue)

大部分的情況下，`PRIMITIVE_COMPARE` 都如預期的運作。唯一的小問題是只有當兩個 token 都有關聯的 `COMPARE_` 定義時才能成立。

```cpp
PRIMITIVE_COMPARE(foo, unfoo) // Should expand to 1, but it expands to 0
```

解決的方法是再增加一層，`IS_COMPARABLE` 先判斷是否兩個 token 都有定義 `COMPARE_`，如果都有再額外使用 `PRIMITIVE_COMPARE` 去比較。

```cpp
#define IS_COMPARABLE(x) IS_PAREN( CAT(COMPARE_, x) (()) )

#define NOT_EQUAL(x, y) \
IIF(BITAND(IS_COMPARABLE(x))(IS_COMPARABLE(y)) ) \
( \
   PRIMITIVE_COMPARE, \
   1 EAT \
)(x, y)
```
透過反轉的 `COMPL` 也可以延伸定義 `EQUAL`。
```cpp
#define EQUAL(x, y) COMPL(NOT_EQUAL(x, y))
```

## Example: generic-print

[generic-print](https://github.com/exebook/generic-print) 是一個有趣的專案，可以用類似 python 的 print 語法來印出字串，搭配容易觀看的顏色功能。雖然它還存在許多使用的限制，但其中的 preprocessor 技巧仍然很值得探討。


### `print`
```cpp
#define fprint(fd, a...) ({ \
        int count = __print_count(a); \
        unsigned short stack[count], *_p = stack + count; \
        __print_types(a); \
        __print_func(fd, count, _p, a); \
})

#define print(a...) fprint(stdout, a)
```

* `({})` 是 [Statements and Declarations in Expressions](https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html)，因為 `__print_func` 不回傳值，在這裡的作用其實可以透過 `do...while(0)` 取代
* `a...` 是 [Variadic Macros](https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html) 語法，表示將一系列由逗號組織起來的參數命名為 `a`

### `__print_count`

```cpp
#define __print_count_int(q,w,e,r,t,y,u,i,o,p,a,s,d,f,g,h,j,k,l,z,x,c,v,b,n,m,...) m
#define __print_count(a...)__print_count_int(a,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)
```

`fprint` 第一個使用到的 macro 是 `__print_count`，其作用是計算參數的數量，讓我們來看看這是如何作用的。上面的作法稍微有點繁瑣，所以下面我們用一個簡化的版本來說明:


```cpp
#define __print_count_int(q,w,e,r,...) r
#define __print_count(a...)__print_count_int(a,3,2,1,0)
```

因為 `r` 總是取第4個參數的內容，因此可以想像給的參數愈多，`3, 2, 1, 0` 就會放的越靠後，讓 `r` 拿到更大數字，巧妙的設計就可以剛會對上正確的數量。舉例來說，讓我們假設 `a` 有兩個參數，例如是 `arg1, arg2`，那麼 `__print_count` 的展開變成 `__print_count_int(arg1, arg2, 3, 2, 1, 0)`，`r` 的位置是第四個參數，所以剛好就是 2。


### `__print_types`

整個 `__print_types` 有點長，這裡就放一個縮減版的，不難猜到省略層層的 pattern 就是減少 `print` 可以接受的參數數量而已。


```cpp
#define __builtin_choose_expr __builtin_choose_expr
#define __print_is_type(a, t) __builtin_types_compatible_p(typeof(a), t)

#define __print_code(a, cont) __builtin_choose_expr(__print_is_type(a, void), \
                0, \
                __print_push(__print_typeid(a), (sizeof(a)<(1<<16>>5)?sizeof(a):(1<<16>>5)-1), cont))

#define __print_types_int(q,w,e,r,...)\
        __print_code(q,__print_code(w,__print_code(e,__print_code(r,0))))

#define __print_types(a...) __print_types_int(a, (void)0,1,1,1)
```
* `__builtin_choose_expr(const_exp, exp1, exp2)` 類似於 `? :` 的概念，只是參數可以是一個 expression，當 `const_exp` 成立時就回傳 `exp1`，反之回傳 `exp2`
*  `__print_is_type(a, t)` 用到 `__builtin_types_compatible_p`，後者的作用是比較 `a` 的 `t` 是否 compatible(可參考 C 語言規格書 6.2.7 Compatible type and composite type)

> [Other Built-in Functions Provided by GCC](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)


`__print_types` 後面的 `(void)0,1,1,1` 是為了填滿  `__print_types_int` 的參數數量。同時也因應 `__print_is_type` 的設計，可以從 `__print_code` 看到 `__print_is_type` 的用法是 `__print_is_type(a, void)`，當 `a` 是 `(void)0` 的時候 `__print_is_type` 會得到 1，而 `__print_code` 的 `cont` 只有在 `__print_is_type` 回傳到 1 時會停止展開。換句話說，在 `(void)0` 以前的參數都會經過 `__print_push` 處理，後者是在 stack 中填入第 `k` 個參數的類型代號和 byte 數量。


### `__print_push`

```cpp
#define __print_push(c,size,cont) (cont, *--_p = c | (size << 5))
```

在前一節可以看到 `__print_push` 的參數來自:
* 第一個參數 `c` 來自 `__print_typeid` 是根據類別所對應的 `type` 編號
* 第二個參數 `size` 是計算自 `(sizeof(a)<(1<<16>>5)?sizeof(a):(1<<16>>5)-1)`
* 第三個參數是 `cont`，是下一個 `__print_code` 的展開，這裡的語法叫作 comma expression，`(exp1,  exp2)` 中 `exp1` 會被計算，而 `exp2` 會被計算且回傳(可參見 C 語言規格書 6.5.17 Comma operator)

可以看到 `size` 是有限制的，因為作者通過一個 16 bits short 表達 type 類型和真正的 size，後 5 bits 儲存類型編碼，所以剩下可以用來儲存 `size` 的範圍只能表達到最大 $2^(16 - 5)$，也就是 `1<<(16>>5)`。

### `__print_func`


```cpp
void __print_func (FILE *fd, int count, unsigned short types[], ...) {
        va_list v;
        va_start(v, types);
```

這裡可以看到再次看到 C 語言的不定長度的參數技巧，簡單說明其使用方式，細節則可以參考 C 語言規格書 7.16 Variable arguments <stdarg.h>。

可以看到首先定義 `va_list`，然後透過 `va_start` 去初始化。`va_start` 透過最後一個參數 `types` 找到不定長度參數的第一個之位址。

```cpp
        ...
        for (int i = 0; i < count; i++) {
                if (i > 0) fprintf(fd, " ");
                char type = types[i] & 0x1F;
                char size = types[i] >> 5;
                if (type == 1) {
                        __print_color(fd, __print_color_float);
                        double d = va_arg(v, double);
                        fprintf(fd, "%'G", d);
                }
        ...
```

然後就是 for 迴圈逐個參數印出，首先取出正確的 `type` 和 `size` 數值。印出的內容可以大致區分成陣列和單個變數的類型，對單個變數來說，先用 `__print_color` 加上顏色的 [ANSI escape code](https://en.wikipedia.org/wiki/ANSI_escape_code)，然後用 `va_arg` 得到變數實際內容後，`fprintf` 印出。

```cpp
                else if (type == 11) {
                    __print_array(fd, int, "%i", __print_color_number);
                }
```
對於陣列類型，舉例來說 type 11 的 `int` array，讓我們來看看 `_print_array` 的實作:

```cpp
#define __print_array(fd, T, qual, color) \
        __print_color(fd, __print_color_normal); \
        int max_len = 16; \
        int n = size/sizeof(T); \
        T *m = va_arg(v, T*); \
        fprintf(fd, "["); \
        __print_color(fd, color); \
        for (int i = 0; i < (n < max_len ? n : max_len); i++) { \
                if (i > 0) fprintf(fd, " "); \
                fprintf(fd, qual, m[i]); \
        } \
        __print_color(fd, __print_color_normal); \
        if (n > max_len) fprintf(fd, "..."); \
        fprintf(fd, "]");

```

首先，用預設的 foreground color (`__print_color_normal`) 印出 `[`(最後的 `]` 也是同樣道理)。接著計算 elements 的數量 `n = size/sizeof(T)`，然後根據指定的顏色一個個輸出，且最多輸出 16 個。