---
# System prepended metadata

title: C 語言：技巧篇
tags: [NCKU Linux Kernel Internals, ' C語言']

---

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

# C 語言：技巧篇

[你所不知道的C語言：技巧篇](https://hackmd.io/@sysprog/c-trick?type=view)


## Return a value rather than modifying pointers

在 [Malcolm Inglis 的 C-style](https://github.com/mcinglis/c-style#prefer-to-return-a-value-rather-than-modifying-pointers) 文章中，對於函式的回傳有如下意見:

```c=
// Bad: unnecessary mutation (probably), and unsafe
void drink_mix( Drink * const drink, Ingredient const ingr ) {
    assert( drink != NULL );
    color_blend( &( drink->color ), ingr.color );
    drink->alcohol += ingr.alcohol;
}

// Good: immutability rocks, pure and safe functions everywhere
Drink drink_mix( Drink const drink, Ingredient const ingr ) {
    return ( Drink ){
        .color = color_blend( drink.color, ingr.color ),
        .alcohol = drink.alcohol + ingr.alcohol
    };
}
```
可以見到，前者是利用修改指標參數得到所要的 Drink 結構，後者則是利用 [designated initializers](https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html) 回傳結構。後者的設計避免了不當的指標操作(不需如前者額外考慮如果傳入的 `Drink *` 是 NULL)，此外，const 僅保證指標本身不能被更動，而指標的 struct 內容則不一定，前者的 `Drink *drink` 等於輸入和輸出必須是同一個 object，後者則可以輸入 A object 然後把 return assign 給 B object，函式的使用彈性更高。

## 初始化 struct 
[Initializing a heap-allocated structure in C](https://tia.mat.br/posts/2015/05/01/initializing_a_heap_allocated_structure_in_c.html) 中提到，動態配置一個 struct 指標時，可能會突然一個手滑，寫成:

```
struct foobar *foobar = malloc(sizeof(struct foobaz));
```
把 `foobar` 寫成 `foobaz`，剛好 `foobaz` 這個結構存在，於是編譯也通過了，然後執行時發生錯誤，你可能需要仔細檢查才能發現自己犯了愚蠢的錯誤。或者 `sizeof(struct foobaz)` 湊巧的和 `sizeof(struct foobar)` 同大小，於是這個錯誤就這樣沉睡在時間的洪流中。

如果更改成
```
struct foobar *foobar = malloc(sizeof(*foobar));
```
看起來好像好多了? 不過 foobar 僅擁有空間而未被初始，使用者如果錯誤的去操作仍會有問題產生。

```
struct foobar *foobar = calloc(1, sizeof(*foobar));
```
上面這樣寫又如何呢? 雖然初始化了，但很多時候我們不是只想把數值初始化成 0 而已。因此文章中給出了建議的解法:

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

struct foobar{
  int a;
  int b;
};

#define ALLOC_INIT(type, ...)   \
        (type *)memdup((type[]){ __VA_ARGS__ }, sizeof(type))
        
void *memdup(const void *src, size_t sz) {
        void *mem = malloc(sz);
        return mem ? memcpy(mem, src, sz) : NULL;
}
int main()
{
   
    struct foobar *foobar  foobar = ALLOC_INIT(struct foobar, {
        .a = 1,
        .b = 2
});
    
    return 0;
}
```
透過這樣的寫法，不僅使用巨集來配置空間並初始化資料結構，也避免了前述的配置錯誤空間大小的問題，例如
```c=
struct foobar *foobar = ALLOC_INIT(struct foobaz, {
        .a = 1
});
```
這樣的錯誤，compiler 會吐出警告而使程式撰寫者察覺可能發生的錯誤。

```c=
__auto_type foobar = ALLOC_INIT(...
```
如果是使用 gcc extension，也可以寫成上述這樣更安全的寫法。

## alloca / strdupa 等使用 stack 配置的空間
如 [alloc](https://man7.org/linux/man-pages/man3/alloca.3.html) 等 **automatically freed** 函式，需注意到得以實現自動釋放的原因是因為把空間配置在 stack 中，而非 heap。因此，可以把生存週期限定在 function 中，如區域變數般的自動把空間釋放掉。然需注意函式本身是有危險性的，linux man page 中敘述:

> RETURN VALUE:
The alloca() function returns a pointer to the beginning of the
allocated space.  If the allocation causes stack overflow, program
behavior is undefined.

可能會因為軟硬體平台差異發生問題。

## strncpy 的疑慮

你可能知道 strcpy 會有非法存取空間的安全疑慮(src 長度大於 dest 、src 沒有 '\0' 等問題)，會有建議改有 strncpy 的說法。然而，strncpy 也不是絕對的安全！

### 問題 1
如果 n 的長度超過 src 本身的長度。

> The C library function char *strncpy(char *dest, const char *src, size_t n) copies up to n characters from the string pointed to, by src to dest. In a case where the length of src is less than that of n, the remainder of dest will be padded with null bytes.

也就是說，如果 src 的長度小於 n，dest 的餘下空間也會被填入 '\0'，這產生了效能上的影響。

### 問題 2
如果 src 長度大於等於 n，而 dest 原本是長度大於 n 的字串，strncpy 的行為並非在長度 n + 1 的地方補上 \0。

```c=
int main()
{
        char a[128] = "Tell me why";
        strncpy(a,"????",4);
        printf("%s\n",a);
        return 0;
}
```
以上面的程式為例，輸出會是 `???? me why` 而不是 `????`。這還只是小事，如果 dest 本身是沒有 \0 的，而此狀況下 strncpy 也不會補上 \0，此時就會產生問題。
## Smart Pointer in C
smart pointer 是 C++ 的指標管理機制，可以避免使用者不當的配置空間產生 memory leak。

C 雖然本沒有提供直接提供直接的語法，但你可以參考 [Implementing smart pointers for the C programming language](https://snai.pe/posts/c-smart-pointers) 自己寫一個。

```c=
#define autofree __attribute__((cleanup(free_stack)))

__attribute__ ((always_inline))
inline void free_stack(void *ptr) {
    free(*(void **) ptr);
}

int main(void) {
    autofree int *i = malloc(sizeof (int));
    *i = 1;
    return *i;
}
```

> [cleanup (cleanup_function)](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html)
The cleanup attribute runs a function when the variable goes out of scope. This attribute can only be applied to auto function scope variables; it may not be applied to parameters or variables with static storage duration. The function must take one parameter, a pointer to a type compatible with the variable. The return value of the function (if any) is ignored.


透過 cleanup 這個 attribute，在變數離開所屬的 scope 時會自動呼叫 free stack，將這個 scope 中配置到的指標所指向的空間也一起釋放。


如果考慮到更複雜的資料結構呢，例如在資料結構下有某個 member 使用了 heap？前面的方法就無法直接的解決問題。 
```c=
#define smart __attribute__((cleanup(sfree_stack)))

struct meta {
    void (*dtor)(void *);
    void *ptr;
};

static struct meta *get_meta(void *ptr) {
    return ptr - sizeof (struct meta);
}

__attribute__((malloc))
void *smalloc(size_t size, void (*dtor)(void *)) {
    struct meta *meta = malloc(sizeof (struct meta) + size);
    *meta = (struct meta) {
        .dtor = dtor,
        .ptr  = meta + 1
    };
    return meta->ptr;
}

void sfree(void *ptr) {
    if (ptr == NULL)
        return;
    struct meta *meta = get_meta(ptr);
    assert(ptr == meta->ptr); // ptr shall be a pointer returned by smalloc
    meta->dtor(ptr);
    free(meta);
}

__attribute__ ((always_inline))
inline void sfree_stack(void *ptr) {
    sfree(*(void **) ptr);
}

struct A{
    int *a;
};

void destructor(void* ptr){
        printf("Help!\n");
        if(ptr != NULL)
                free(((struct A*)ptr)->a);
}
int main(void) {
    smart struct A *i = smalloc(sizeof(struct A),destructor);
    i->a = malloc(sizeof(int));
    return 0;
}
```
因此文章中透過使用上述程式的技巧，讓使用者可以定義自己的 destructor，在離開 scopes 時就可以透過這個 destructor 回收所有需要用到的空間！

:::info
上面的 destructor 其實寫得有點隨便，僅僅提供大致的概念。原文中有比較詳細的說明，[Github](https://github.com/Snaipe/libcsptr) 中也有更豐富的設計內容，有興趣的人務必直接參考原文！
:::

## ARRAY_SIZE Macro
如果要定義一個計算矩陣大小的巨集，應該寫成如何呢？

```c=
#define ARRAY_SIZE(arr)    (sizeof(arr) / sizeof((arr)[0]))
```
看似很合理，但如果考慮下列程式：
```c=
int main(void)
{
        int a[10];
        int *a_ptr = a;
 
        // Is these two the same ?
        printf("%d\n", ARRAY_SIZE(a));
        printf("%d\n", ARRAY_SIZE(a_ptr)); 
 
        return 0;
}
```
第二個 printf 可不會印出 10!問題就藏在 `sizeof(a_ptr)` 會得到的是`指標的大小`。以 64 位元架構而言，無論 a[n] 的 n 到底是多少，就只
會得到 64 / 8 = 8 而已。

為了避免誤用，linux kernel 中的 `ARRAY_SIZE` 是這樣寫的：

``` c=
#define BUILD_BUG_ON_ZERO(e) (sizeof(char[1 - 2 * !!(e)]) - 1)
#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
```

讓我們一一探討：
*  `BUILD_BUG_ON_ZERO(e)` 中，`1 - 2 * !!(e)`: 對 condition e 取反兩次，因此 `!!(e)` 只會是 0 或者 1，`1 - 2 * !!(e)` 就只會是 0 或 -1，而 sizeof(char[-1])會使編譯器吐出錯誤訊息
* `__same_type(a, b)` 使用`typeof()` 取得 a, b 的型態，`__builtin_types_compatible_p()` 會檢查兩個型態是否相同，相同則為 1，不同則為 0 
* `__must_be_array` 中就是比較 arr 本身的型態，與 `&(a)[0]` (如果是 array 會降成指標型態，是指標則維持) 是否有差別。
* 因此如果輸入的是陣列， __same_type(a, b) 得到 0，BUILD_BUG_ON_ZERO(e) 中 1 - 2 * !!(e) 得到 1 ，通過編譯
* 如果輸入的是指標， __same_type(a, b) 得到 1，BUILD_BUG_ON_ZERO(e) 中 1 - 2 * !!(e) 得到 -1 ，則無法通過編譯

藉此，ARRAY_SIZE 透過額外的檢查，如果使用者不當的使用，在編譯時期就會收到如下的錯誤訊息：
 ```
 error: size of unnamed array is negative
 ```
 
[Linux Kernel: ARRAY_SIZE()](https://frankchang0125.blogspot.com/2012/10/linux-kernel-arraysize.html)


## Variable Length Arrays
* C99 支援執行時期才知道陣列長度
*  [Arrays of Length Zero](https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html) 用法： 可以動態配置 struct 中的陣列長度。

```c=
struct line {
  int length;
  char contents[0];
};

int main(void)
{
    struct line *thisline = (struct line *)malloc (sizeof (struct line) + 8);
    thisline->length = 8;

    return 0;
}

```

## Integer to string
[Integer to string conversion](https://tia.mat.br/posts/2014/06/23/integer_to_string_conversion.html)

## do { ... } while(0) 
do { ... } while(0) 看起來很多餘，對嗎？ 不妨先回答下面的問題

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

#define add_two_num(x,y) x++;y++

int main(void)
{
    int a = 0, b = 100;
    if(a == b)
        add_two_num(a,b);

    printf("%d %d\n",a,b);
 
    return 0;
}
```
printf 應該要印出甚麼？如果你認為是 0 100，那可能要大失所望了，這裡會印出的數字是 0 101。

仔細看就知道原因，如果把巨集展開，寫成比較不會誤解的寫法，其實會變成
```c=
#include <stdio.h>

#define add_two_num(x,y) x++;y++

int main(void)
{
    int a = 0, b = 100;
    if(a == b)
        a++;
    
    b++;

    printf("%d %d\n",a,b);
 
    return 0;
}
```
因為 `x++;y++` 並沒有被 scope 包圍，因此事實上只有 `x++` 是跟著上面的 if，而 `y++` 則獨立執行。

你可能會想說怎麼不寫成
```c=
#define add_two_num(x,y) {x++;y++;}
```
但是如果今天是寫成
``` c=
if(a == b)
    add_two_num(a,b);
else
    ...
```
此時編譯就會無法通過了(else 會找不到自己對應的 if)。為了有一個方便使用 macro，我們可以利用 do { ... } while(0) 來實作。

```c=
#define add_two_num(x,y) do{x++;y++;}while(0)
```
將 macro 寫成上述形式，就可以滿足我們想要的使用了。

## Prevent double evaluation

思考以下程式，應該要印出甚麼？
```c=
#include <stdio.h>
#include <stdlib.h>

#define max(a, b) (a > b ? a : b)
 
int foo(int *b){
        (*b)++;
        return (*b);
}

int main(void)
{
    int a = 0, b = 100;
    max(foo(&a),foo(&b));
    printf("%d %d\n",a,b);
 
    return 0;
}
```
答案是 1 102。雖然驟看 `foo` 似乎只對 a、b各自呼叫一次，但把 max 展開其實是：
```c=
foo(&a) > foo(&b) ? foo(&a) : foo(&b)
```
一個 max 中總共會呼叫三次 `foo`，然而這可能不是我們所預期的 max 行為。

我們可以使用 GNU extension typeof 來解決此問題，將 max 寫成：
```c=
#define max(a, b)     \
{(typeof(a) _a = a;   \
  typeof(b) _b = b;   \
  _a > _b ? _a : _b;) \
}
```
先透過 `typeof` 定義暫存的變數 `_a` 、 `_b`，存放函式的回傳值(如果 a、b 是一個純數值也可以直接 assign)，再透過暫存的 `_a` 、 `_b` 進行比較。

