CS:APP Ch7 Linking 學習筆記

更新版修改於 https://hackmd.io/@haogroot/cs-app_ch7

為什麼要學習 Linking

  • 理解 linker 幫助你建構大型程式
    • 大型程式會包含許多 libraries ,了解如何 linking 可以幫助你處理棘手的編譯錯誤。
  • 理解 linker 可以避免寫程式上犯下難以抓出的錯誤
    • linker 執行 symbol resolution 所做的決定將大大的影響程式執行。
  • 理解 linking 幫助你理解 scope 的概念
    • global 跟 local variable 之間的差別
    • static 的作用
  • 理解 linking 幫助你理解重要的系統概念
  • 理解 linking 讓你更理解如何使用 shared library
  • 我個人在工作上常常遇到 Linker 報出的錯誤,深感完全不理解其背後的運作原理,所以決定將這張拿出來好好學習。

閱讀全文〈CS:APP Ch7 Linking 學習筆記〉

漫談 linked list 在 linux kernel 中的不一樣

linked list 是大家在學資料結構一定會學到的,通常會將資料跟指標寫在同一個 struct,但在 linux kernel 內卻不是這樣使用的。

Linux kernel 內的定義

一般來說 linked list 在 linux kernel 都以 struct list_head 來描述。

tools/include/linux/types.h 可以見到其定義:

struct list_head {
        struct list_head *next, *prev;
    };

這個 struct 沒有帶任何的 data,Kernel 用法會將它直接放到其他 struct 內,使得其他 struct 也可以擁有 linked list 的操作。

我們來看一個例子,在 linux/net/netlabel/netlabel_addrlist.h 可以見到這樣用法

    /**
     * struct netlbl_af6list - NetLabel IPv6 address list
     * @addr: IPv6 address
     * @mask: IPv6 address mask
     * @valid: valid flag
     * @list: list structure, used internally
     */
    struct netlbl_af6list {
        struct in6_addr addr;
        struct in6_addr mask;
        u32 valid;
        struct list_head list;
    };

疑~ 等等 ! 那這樣就算我們走訪整個 linked list,我們也存取不到 data,這樣我們就失去使用 linked list 的意義了!

一般在走訪到每個 list node ,透過指標就可以直接存取 data ,如以下作法:

    ListNode *current = head;
    while(current != 0) {
        printf("%d", current->data);
        current = current->next;
    }

由於 kernel 並沒有把 data 跟 pointer 放在同樣的 struct,造成無法直接存取 data ,以下就來探討 kernel 是如何解決的問題。

如何走訪 linked list 並存取資料

kernel 提供 containerof 這個巨集來解決上述問題,以下我們就來拆解

    #define container_of(ptr, type, member)                            \\
        __extension__({                                                \\
            const __typeof__(((type *) 0)->member) *__pmember = (ptr); \\
            (type *) ((char *) __pmember - offsetof(type, member));    \\
        })
  • offsetof(type, member) 這個 macro 定義在標準函式庫裡,通過把 0 位址轉換為 type 型態的 pointer,然後去獲取該 struct 中的 member 的 pointer,也就是獲取了 member 與該 struct 起始點的 offset。
  • typeof 可以用在 expression 或 type 上,此 macro 會回傳 argument 的 type,值得留意的是,使用 expression 作為 argument 上並不會真的執行這個 expression。
    typeof (int *) m
    typeof (typeof (char *)[4]) n;

上述等同於宣告 int m 和 char n[4] 一樣。

  • (type *)0 -> 這個 type 形態的 struct,他裡面有個成員叫做 member,我們利用 typeof 取得 member 的型態。
  • 再用 __pmember 減去 offset,也就等於得到了該 type struct 的真實位址。
  • 因此 container_of 我們可以理解為我們利用其成員來反求其母結構的位址
  • __ extension __
    GCC uses the extension attribute when using the -ansi flag to avoid warnings in headers with GCC extensions.

透過 container_of 讓我們能夠在走訪 linked list 時候隨時都可以得到其所在之 struct 的位址,一但有了位址,就能夠自由的存取他的所有成員變數了。

Updated on 2022-07-27 08:04:21 星期三

淺談 C 語言的型態轉換

前言

最近公司專案導入 static code check tool 並且採用較嚴格的 coding rule 來檢查,團隊內不乏許多資深的工程師,但檢查出相當多型態轉換的問題,促使我想寫下這篇文章釐清 C 語言型態轉換的規則及可能的問題。

可能的安全問題

C語言包含不同的型態,不同型態之間做運算就會適用不同規則,這也是 C 語言型態轉換困難的地方,進行型態轉換,可能會發生以下問題是值得我們留意的

  • loss of value: 如果今天我們將 integer 轉換到 char 型態,由於 int 型態大小遠大於 char,就會造成這種狀況
  • loss of sign: 如果我們將 unsigned int 與 signed int 進行運算,原本 signed int 被迫轉換成 unsigned int,就會造成這種狀況
  • loss of precision: 從 floating 型態轉換到 integer 型態
  • loss of layout: 型態轉換發生在從 pointer to struct 到 pointer to char

型態轉換又分為兩種:

  1. explicit conversion: 就是大家會在教科書上看到的 cast,開發者可以強制轉換任何資料的 data type。
  2. implicit conversion: 這是大部分開發者都會疏忽的,由 compiler 來幫你轉換型態,以下會描述什麼情況下會發生 implicit conversion,我們並不需要牢記這些規則,但必須要在開發時候隨時留意。

閱讀全文〈淺談 C 語言的型態轉換〉