# [AIdrifter CS 浮生筆錄](https://hackmd.io/s/rypeUnYSb) <br> 2016 年,現代 C 語言的寫法
:::info
寫 C 的首要原則,就是避免去寫 C。
(The first rule of C is don’t write C if you can avoid it.)
:::
## c99 vs c11
- c99:「1999年制定的標準」
- c11:「2011年制定的標準」
## Compiler
C 的版本建議使用 C11,再來是 C99。編譯器建議使用 clang,再來是 gcc。編譯時可加上 `-std=c11` 選項使用 C11 編譯,例如:
```shell
$ clang -std=c11 main.c -o justDoIt
```
- clang 默認是用`-std=c11`
### Optimzie
- -O2,-O3
- 通常我們需要使用`-O2`優化級別,但是有的時候我們希望使用`-O3`優化級別。可以在這兩種優化級別(和跨編譯器)下的測試之後,保留最佳性能的二進制代碼。
- -Os
- `-Os`優化級別能夠幫助提高緩存性能(它應該是)
### Warning
- -Wall -Wextra -pedantic
新版本編譯器 提供了`-Wpedantic`開關,但是為了向下兼容,它們仍然支持古老的-pedantic開關。
- 在測試工程中,應該在全平台中添加`-Werror`和`-Wshadow`開關
- 在部署產品源碼時,使用-Werror開關可能會非常棘手,因為不同的平台和編譯器可能會發出不同的警告信息。我們可能不希望因為平台上的GCC版本有從未看見過的警告而中止用戶的整個構建。
- 其他花哨的選項`-Wstrict-overflow` `-fno-strict-aliasing`
- 要麼通過`-fno-strict-aliasing`開關或者確保只使用對象創建時的類型來訪問對象。由於大量已經存在的C代碼使用了跨類型別名,如果我們無法控制源碼樹,使用`-fno-strict-aliasing`開關會更加安全。
- 截至目前,clang會將一些有效語法作為警告,因此我們應該增加-Wno-missing-field-initializers開關
- GCC在4.7.0版本後修復了這個無效的警告
### CPU Architecture
- -march=native
- 授權編譯器使用CPU的所有特性指令集
- 同樣,性能測試和回歸測試(比較跨編譯器或者編譯器版本)非常重要,以確保啟動了優化之後沒有副作用。
- -msse2和-msse4.2對於需要使用非構建機器構建目標文件可能會有用。
## Declare
### 最小類型
- 帶正負號的整數使用 `int8_t`、`int16_t`、`int32_t`、`int64_t`
- 不帶號的整數使用 `uint8_t`、`uint16_t`、`uint32_t`、`uint64_t`
- 32 與 64 位元長的浮點數使用 `float` 與 `double`
### 使用char場景
- `uint8_t` 取代 `char` 來宣告不帶號的 8 位元整數
- char 保留在對於字元或字串(char *)的宣告上就好。
### 使用integer場景
- 整數的話直接宣告 `intmax_t` 或 `uintmax_t`,浮點數則用 `double` 即可
- 另外在 `stddef.h` 中有所有謂的 `size_t`,可以用來宣告==最大==的`「不」帶號整數`。依 C99 的定義,size_t 是個可以做為任何長度的陣列的索引(index)。至於要不要使用它則隨意了。
### 使用pointer
- 對於指針運算的正確方式是使用`<stdint.h>`頭文件中定義的`uintptr_t`類型,同時也可以使用`stddef.h`頭文件中定義的`ptrdiff_t`類型。
- Bad
```C
long diff = (long)ptrOld - (long)ptrNew;
```
- Good
```C
ptrdiff_t diff = (uintptr_t)ptrOld - (uintptr_t)ptrNew;
```
- 同時,如果需要輸出內容:
```C
printf("%p is unaligned by %" PRIuPTR " bytes.\n", (void *)p, ((uintptr_t)somePtr & (sizeof(void *) - 1)));
```
### 32 bits vs 64 bits
- 在這種情況下,我們應該使用intptr_t類型——定義了當前平台上字長的整數。
- 在32位平台上,`intptr_t`是`int32_t`類型。
- 在64位平台上,`intptr_t`是`int64_t`類型。
- 同時,`intptr_t`還有對應的無符號類型`uintptr_t`。
- 對於指針偏移量,我們有一個更加恰當的類型:`ptrdiff_t`,它是存儲指針差值的正確類型。
### 現在compiler支援 #paragma once
- Bad
```C
#ifndef PROJECT_HEADERNAME
#define PROJECT_HEADERNAME
.
.
.
#endif /* PROJECT_HEADERNAME */
```
- Good
```C
#pragma once
```
## 不要把判斷式寫在宣告local變數後面
- Bad
```C
void test(uint8_t input)
{
uint32_t b;
if (input > 3) {
return;
}
b = input;
}
```
- Good
```C
void test(uint8_t input)
{
if (input > 3) {
return;
}
uint32_t b = input;
}
```
## C99 允許在 for 迴圈宣告計數器
```C
for (uint32_t i = 0; i < 10; i++)
```
## Array 初始化
- Bad
```C
uint32_t numbers[64];
memset(numbers, 0, sizeof(numbers));
```
- Good
```C
uint32_t numbers[64] = {0};
```
## Structure
### Initailize
```C
struct thing
{
uint64_t index;
uint32_t counter;
};
```
- Bad
```C
struct thing localThing;
void initThing(void)
{
memset(&localThing, 0, sizeof(localThing));
}
```
- Good
```C
struct thing localThing = {0};
```
### 如果需要重新初始化已經分配內存空間的結構體,可以定義一個全局空結構體,然後賦值:
```C
struct thing {
uint64_t index;
uint32_t counter;
};
static const struct thing localThingNull = {0};
.
.
.
struct thing localThing = {.counter = 3};
.
.
.
localThing = localThingNull;
```
### 複合字面量(compound literals)
```C
localThing = (struct thing){0};
```
Reference:
[The New C: Compound Literals](http://www.drdobbs.com/the-new-c-compound-literals/184401404)
## 讓fucntion可以輸入任意數值,別限制他的型別。
- input 宣告成 `void *` , 自己再用`uint8_t`重新去assign。
```C
void processAddBytesOverflow(void *input, uint32_t len)
{
uint8_t *bytes = input;
for (uint32_t i = 0; i < len; i++) {
bytes[0] += bytes[i];
}
}
```
## Return Type
- true, false 請`incldue <stdbool.h>`
- 用enum創造複合型態的回傳型別。
```C
typedef enum growthResult {
GROWTH_RESULT_SUCCESS = 1, // Success
GROWTH_RESULT_FAILURE_GROW_NOT_NECESSARY, // failure 1
GROWTH_RESULT_FAILURE_ALLOCATION_FAILED // failure 2
} growthResult;
```
## Coding Style
- clang-format
- translate main.c to clang format type
```shell
clang-format -style="{BasedOnStyle: llvm, IndentWidth: 4, AllowShortFunctionsOnASingleLine: None, KeepEmptyLinesAtTheStartOfBlocks: false}" "$@" -i main.c
```
- modify `~/.bashrc`
```
alias clang-format='clang-format -style="{BasedOnStyle: llvm, IndentWidth: 4, AllowShortFunctionsOnASingleLine: None, KeepEmptyLinesAtTheStartOfBlocks: false}" "$@"'
```
- finally
```shell
clang-format -i main.c
```
## Memory
- `calloc()` is replaced with `malloc()`
- To be avoid to use `memset()`
## Reference
https://matt.sh/howto-c
http://www.infoq.com/cn/articles/c-language-2016