vscode調試redis源碼

###########

衆所周知,redis是C語言寫的,代碼整潔優雅,可讀性強

閱讀 Redis 源碼需要一定的專業知識和經驗,以下是一些建議:

  1. 熟悉 C 語言: Redis 使用 C 語言編寫,因此需要熟練掌握 C 語言的語法和特性。

  2. 瞭解 Redis 的設計思想和架構:Redis 是一個基於內存的高性能鍵值存儲系統,需要了解 Redis 的數據結構、事件驅動模型、內存管理等核心特性。

  3. 熟悉 Redis 的源碼結構:Redis 的源碼結構比較清晰,可以從主函數開始逐步深入,瞭解每個模塊的實現細節。

  4. 閱讀 Redis 的文檔和註釋:Redis 的源碼中有很多註釋和文檔,可以幫助理解每個函數的作用和實現方式。

  5. 使用調試工具:使用調試工具可以更方便地瞭解 Redis 的運行過程和調試代碼。

  6. 參考其他開發者的經驗:可以參考其他 Redis 開發者的經驗和建議,掌握更多閱讀 Redis 源碼的技巧和方法。

總之,閱讀 Redis 源碼需要耐心和細心,逐步深入瞭解 Redis 的實現細節,才能更好地理解 Redis 的運行機制和優化方式。

準備工作

  1. centos上配置我mac的ssh密鑰,且下載redis源代碼:git clone [email protected]:redis/redis.git
  2. 在mac上安裝好vscode,並安裝必要的插件:Remote-ssh
  3. 在mac上的vscode上登陸centos並打開redis源代碼

 

 

make CFLAGS="-g -O0" 

 

在.vscode目錄下創建兩個文件:launch.json和tasks.json

launch.json文件:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) 啓動",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/src/redis-server",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "爲 gdb 啓用整齊打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "將反彙編風格設置爲 Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ]
        },
    ]
}

 

 tasks.json文件:

{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "shell",
            "label": "build",
            "command": "make",
            "args": [
                "CFLAGS=\"-g -O0\"",
            ],
            "problemMatcher": [],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

 

 ########################

在C語言中,頭文件的作用是在源代碼中引入外部的聲明和定義,以便在源代碼中使用這些定義。當頭文件被包含在多個源代碼文件中時,爲了防止重複定義,可以使用條件編譯指令來避免問題。

#ifndef 是預處理指令中的條件編譯指令之一,意思是“如果沒有定義這個宏,則執行下面的代碼,否則跳過這段代碼”。#define 是定義一個宏,在這裏是定義 __SDS_H 這個宏。#endif 則是結束條件編譯指令塊。

當一個頭文件被多個源代碼文件包含時,使用條件編譯指令可以避免重複定義的問題。如果不這樣使用,頭文件中的內容會被多次定義,導致編譯錯誤。舉個例子:

// file1.c
#include "example.h"

// file2.c
#include "example.h"

如果 example.h 文件沒有使用條件編譯指令,而其中包含了定義函數或變量的語句,那麼這些函數或變量就會在編譯時被重複定義,導致編譯錯誤。而使用條件編譯指令,可以確保 example.h 文件只會被編譯一次,避免了重複定義的問題。

 

####################

在C語言中,使用const修飾函數參數表示該參數是隻讀的,不允許在函數中修改它所指向的內容。而沒有使用const修飾的函數參數則可以在函數中進行修改。

具體來說,func1(const char *init)中的參數init是一個指向常量字符的指針,也就是說,在函數中不能修改init所指向的內容。而func2(char *init)中的參數init是一個指向字符的指針,函數中可以修改init所指向的內容。

下面舉例說明:

#include <stdio.h>

void func1(const char *str) {
    // 下面這行代碼會導致編譯錯誤,因爲 str 是隻讀的
    // str[0] = 'A';
    printf("%s\n", str);
}

void func2(char *str) {
    str[0] = 'A';
    printf("%s\n", str);
}

int main() {
    char str[] = "hello";
    func1(str);
    func2(str);
    return 0;
}

在上面的代碼中,我們定義了兩個函數func1func2,分別使用const和非const修飾參數。在main函數中,我們聲明一個字符串str,並分別傳遞給兩個函數進行測試。

如果我們嘗試在func1中修改str,則會導致編譯錯誤。而在func2中修改str則沒有問題。

 

頭文件中的static函數與static inline函數的區別

(1)在c語言中, myfunc.h頭文件如下:

static int add(int a, int b) {
    return a + b;
}

那麼其他任意的c文件只要包含了該頭文件都能使用add函數嗎?

  不是的。因爲 static 關鍵字修飾的函數只能在當前文件內部使用,其他文件無法訪問該函數。所以,其他任意的 C 文件包含了該頭文件,編譯器也無法將該頭文件中的 add 函數的定義暴露給其他文件。如果需要在其他文件中使用該函數,應該使用 extern 關鍵字聲明該函數,並將其定義放到一個 C 文件中。 

 

static 函數指的是在當前編譯單元中,該函數只能被當前文件內的其他函數所調用,不能被其他文件調用,因爲它的作用域被限制在當前文件內。在頭文件中定義 static 函數是爲了限制該函數只能在當前文件內使用,防止函數被其他文件所調用。

以下是一個使用 static 函數的示例:

/* 在 myfunc.c 文件中定義了一個 static 函數 */
static int add(int a, int b) {
    return a + b;
}

/* 在 main.c 文件中,使用了 myfunc.h 頭文件,但是並不能使用 myfunc.c 中的 static 函數 add */
#include "myfunc.h"

int main() {
    int sum = add(1, 2); // 編譯報錯,add 函數未定義
    return 0;
}

extern 函數指的是函數的定義在當前編譯單元外,需要通過其他文件引用該函數。extern 關鍵字可以省略,因爲函數默認是 extern 的。

以下是一個使用 extern 函數的示例:

/* 在 myfunc.c 文件中定義了一個函數 */
int add(int a, int b) {
    return a + b;
}

/* 在 myfunc.h 頭文件中,聲明瞭 add 函數 */
#ifndef MYFUNC_H
#define MYFUNC_H

extern int add(int a, int b);

#endif

/* 在 main.c 文件中,使用了 myfunc.h 頭文件,並且通過該頭文件成功引用了 myfunc.c 中的 add 函數 */
#include "myfunc.h"

int main() {
    int sum = add(1, 2); // 調用 myfunc.c 中的 add 函數
    return 0;
}

注意,在實際開發中,爲了避免出現重複定義的錯誤,通常將函數的定義放在 .c 文件中,將函數的聲明放在 .h 頭文件中,並在需要調用該函數的文件中包含對應的頭文件。同時,在頭文件中對函數進行聲明時,可以使用 extern 關鍵字顯式聲明爲外部函數。

 

 

(2)在c語言中, myfunc.h頭文件如下:

static inline int add(int a, int b) {
    return a + b;
}

那麼其他任意的c文件只要包含了該頭文件都能使用add函數嗎?

是的,其他任意的c文件只要包含了該頭文件都能使用add函數。由於該函數被定義爲static inline,編譯器會在編譯時將函數的代碼直接嵌入到調用處,而不會生成獨立的函數代碼,從而實現了函數的內聯。因此,其他c文件在包含該頭文件後,也能夠直接使用該函數,而無需進行鏈接操作

 

 

 一個源文件中使用了頭文件中聲明的函數或變量,那麼是如何找到的呢?

如果C語言代碼中調用了某個函數,但是該函數的定義並沒有在當前文件中出現,編譯器在編譯階段只會檢查函數的聲明,如果該函數沒有被聲明過,會產生編譯錯誤。在鏈接階段,編譯器需要找到該函數的定義,否則會產生鏈接錯誤。

/* 在 myfunc.c 文件中定義了一個函數 */
int add(int a, int b) {
    return a + b;
}

/* 在 myfunc.h 頭文件中,聲明瞭 add 函數 */
#ifndef MYFUNC_H
#define MYFUNC_H

extern int add(int a, int b);

#endif

/* 在 main.c 文件中,使用了 myfunc.h 頭文件,並且通過該頭文件成功引用了 myfunc.c 中的 add 函數 */
#include "myfunc.h"

int main() {
    int sum = add(1, 2); // 調用 myfunc.c 中的 add 函數
    return 0;
}

在上述代碼中,main.c 文件中使用了 #include "myfunc.h" 引入了 myfunc.h 頭文件,頭文件中聲明瞭 extern int add(int a, int b),這告訴編譯器有一個 add 函數存在,並且需要從其他地方尋找它的定義。

當編譯器在編譯 main.c 文件時,它會在鏈接階段查找 add 函數的定義。如果在鏈接階段沒有找到 add 函數的定義,鏈接器將會產生一個鏈接錯誤。因此,在這個例子中,編譯器不會在編譯期間查找 add 函數的定義,而是在鏈接期間進行查找。

通常,在編譯 myfunc.c 文件時,編譯器將創建一個目標文件,其中包含了 add 函數的實現代碼。當鏈接器將這個目標文件與 main.c 文件一起鏈接時,它會在 myfunc.c 目標文件中找到 add 函數的定義,並將其與 main 函數中的調用匹配起來。

因此,雖然 main.c 文件中沒有包含 myfunc.c 文件,但是通過頭文件中的函數聲明,編譯器和鏈接器能夠找到 add 函數的定義並將其與 main 函數中的調用匹配起來。

在編譯過程中,編譯器會對每個源文件單獨進行編譯生成目標文件,對於使用了外部函數或變量的源文件,編譯器只會檢查函數或變量是否存在對應的聲明,而不會檢查是否有定義。如果存在外部函數或變量的引用,編譯器會在目標文件中生成一個符號表,記錄下函數或變量的名字和類型等信息,但是不包含函數或變量的實際代碼或數據。

在鏈接過程中,鏈接器會將各個目標文件進行鏈接生成可執行文件或共享庫等,鏈接器會遍歷所有目標文件中的符號表,將所有函數和變量名字進行比對和匹配。對於外部函數或變量的引用,鏈接器會在所有目標文件中查找該函數或變量的定義,如果找到,則將引用替換爲實際的地址或偏移量。如果找不到,則鏈接失敗,拋出未定義符號的錯誤。

在上面的例子中,當編譯器編譯 main.c 文件時,發現調用了 add 函數,但是在該文件中沒有找到該函數的定義。然後編譯器會查找是否存在該函數的聲明,在 myfunc.h 頭文件中找到了 extern int add(int a, int b); 聲明,於是在目標文件中生成一個符號表記錄下 add 函數的名字和類型等信息。當鏈接器鏈接 main.o 和 myfunc.o 兩個目標文件時,發現兩個目標文件都有一個名爲 add 的符號表,於是鏈接器會將兩個符號表進行比對和匹配,找到 myfunc.o 中的 add 函數的定義,將 main.o 中的 add 函數的引用替換爲實際的地址或偏移量,最終生成可執行文件。

 

在CentOS中,可以通過以下方式查找C語言項目引用的第三方庫、頭文件、函數定義的源文件:

  1. 查找頭文件

在C語言代碼中,可以通過include指令引用頭文件。在CentOS中,頭文件通常位於/usr/include目錄下,可以通過以下命令查找某個頭文件的位置:

$ locate header_file_name.h

 

如果該頭文件不存在,可能需要安裝相關的開發包。

  1. 查找庫文件

在C語言代碼中,可以通過鏈接器引用庫文件。在CentOS中,庫文件通常位於/usr/lib或/usr/local/lib目錄下。可以通過以下命令查找某個庫文件的位置:

$ locate library_name.so

 

如果該庫文件不存在,可能需要安裝相關的開發包。

  1. 查找函數定義的源文件

如果C語言代碼中調用了某個函數,但是該函數的定義並沒有在當前文件中出現,那麼編譯器在鏈接階段需要找到該函數的定義。可以通過以下步驟查找函數定義的源文件:

  • 首先找到函數所在的庫文件,如libxxx.so。
  • 然後使用nm命令查看該庫文件中的符號表,如下所示:
$ nm -D libxxx.so | grep function_name

 

其中,function_name是需要查找的函數名。

  • 如果符號表中存在該函數,那麼就可以得到該函數的地址,如0x12345678。
  • 最後使用addr2line命令查找該地址對應的源文件和行號,如下所示:
$ addr2line -e libxxx.so 0x12345678

其中,libxxx.so是庫文件名,0x12345678是函數地址。

如果需要查找標準庫函數的定義,可以通過man命令查看函數的文檔,其中包含了函數所在的頭文件和庫文件。

 

##########

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章