[C 的那些眉角]全域變數的誘惑 — 為什麼要克制使用

有一次接手一個前人留下來的 firmware 專案,開機卡在某個地方,重現條件很怪:只有在 UART 收到特定指令、又剛好在某個 timer 中斷觸發的時候才會掛。

追了兩天,最後發現是一個叫 g_status 的全域變數,被三個地方在改:主迴圈、UART ISR、還有一個 timer callback。三方搶著寫,沒有人管同步。

那一刻我就在想,當初寫這個的人,大概也只是想「方便」而已。

閱讀全文

[C 的那些眉角]sizeof 的陷阱 — 指標傳進函式就失效了

有一次在專案開發中,需要把收到的封包做 checksum 驗證。邏輯很單純,把 buffer 的每個 byte 加起來,對吧?

我寫了一個驗證函式,丟進去跑,結果 checksum 每次都不對。封包明明是對的,Wireshark 抓出來也沒問題,但我的函式就是算錯。

花了快一個小時在懷疑通訊協定、懷疑 byte order、懷疑硬體……最後發現問題出在 sizeof。一個我以為自己很熟的東西。

問題長這樣

簡化後的程式碼大概是這樣:

閱讀全文

[C 的那些眉角]NULL 不等於 0 — 指標的初始化問題

寫韌體寫久了,有些東西會變成肌肉記憶,手就是會自動打出來。指標宣告完順手 = NULL,這大概是每個 C 工程師的本能反應。

但前陣子在 debug 一個詭異的 crash,追了大半天才發現:問題出在我對 NULL0、和「指標初始化」這幾件事的理解,其實一直有個模糊地帶。


事情是怎麼開始的

在一個專案裡,有一段大概長這樣的 code:

struct sensor_ctx {
    uint8_t *buf;
    uint16_t len;
    int (*callback)(uint8_t *data, uint16_t size);
};

void sensor_init(struct sensor_ctx *ctx)
{
    memset(ctx, 0, sizeof(*ctx));
    // 後面根據設定去 assign buf 和 callback
}

看起來超正常對吧?memset 把整個 struct 清成 0,指標欄位自然就是 NULL,之後再根據需要 assign。

這段 code 在我們的 ARM Cortex-M4 平台跑了好幾個月都沒事。直到有一天,同事把類似的邏輯搬到另一個比較冷門的平台上——然後 callback 呼叫就莫名 crash 了。

閱讀全文