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 git@github.com: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命令查看函数的文档,其中包含了函数所在的头文件和库文件。

 

 

一、C/C++头文件路径

搜索规则:当不同目录下存在相同的头文件时,先搜到那个使用哪个

1、尖括号

如果你引用的头文件是标准库的头文件或官方路径下的头文件,一般使用尖括号 <> 包含;

以 #include <xx.h> 为例,当我们使用尖括号 <> 包含一个头文件时,头文件的搜索顺序为:

  1. 参数控制:gcc-I 指定的目录(注:大写的 i)
  2. 环境变量:C_INCLUDE_PATH,CPLUS_INCLUDE_PATH指定的目录( .bash_profile 或者 .bashrc 或/etc/profile)
  3. 系统标准头文件路径:/usr/include和/usr/local/include

2、双引号

如果你使用的头文件是自定义的或项目中的头文件,一般使用双引号 "" 包含。头文件路径一般分为绝对路径和相对路径

当我们使用双引号 “” 来包含头文件路径时,编译器会首先到项目当前目录去搜索需要的头文件,在当前项目目录下面搜不到,再到其他指定的路径下面去搜索:

  1. 源文件当前目录
  2. 参数控制:gcc-I 指定的目录
  3. 环境变量 :C_INCLUDE_PATH,CPLUS_INCLUDE_PATH指定的目录( .bash_profile 或者 .bashrc 或/etc/profile)
  4. 系统标准头文件路径:/usr/include和/usr/local/include

 

3、linux系统标准头文件的路径

一般为:/usr/include和/usr/local/include

再真正的使用过程中,可以使用cpp -v命令查看标准的头文件路径,

结果如下:

[root@wy3-db245 openssl]# cpp -v
Using built-in specs.
COLLECT_GCC=cpp
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla 
--enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions
--enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto
--enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install
--with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
Thread model: posix gcc version 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC) COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64' /usr/libexec/gcc/x86_64-redhat-linux/4.8.5/cc1 -E -quiet -v - -mtune=generic -march=x86-64 ignoring nonexistent directory "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/include-fixed" ignoring nonexistent directory "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/include" #include "..." search starts here: #include <...> search starts here: /usr/lib/gcc/x86_64-redhat-linux/4.8.5/include /usr/local/include /usr/include End of search list.

 

除了系统默认的include路径外,就是设置上面的 C_INCLUDE_PATH 和 CPLUS_INCLUDE_PATH 环境变量来添加标准系统头文件路径。

三、链接库文件的路径

1.系统默认的链接库文件的路径有:

/lib

/usr/lib

/usr/local/lib

#  (对于64位的库,则是以lib64开头的)

 

2.设置链接库文件的路径

(1)环境变量:

在环境变量中添加。

全局设置:/etc/profile

当前用户设置:.bash_profile或者.bashrc

动态链接库搜索路径:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库文件路径

静态链接库搜索路径:

export LIBRARY_PATH=$LIBRARY_PATH:库文件路径

<注意> 当通过上述环境变量指定多个动态库搜索路径时,路径之间用冒号 “:” 分隔。

以上修改可以直接在Linux命令行输入(一次性)

也可以在/etc/profile中完成(对所有用户生效)

也可以在~/.bash_profile或者~/.bashrc中添加(针对某个用户生效),修改完成后,使用source命令使修改立即生效。

 

(2)配置文件:

/etc/ld.so.conf 中添加指定的链接库搜索路径(需要root权限),然后运行 /sbin/ldconfig 命令,以达到刷新 /etc/ld.so.cache 的效果。ldconfig 命令的作用就是将 /etc/ld.so.conf 指定的路径下的库文件缓存到 /etc/ld.so.cache 。

因此当安装完一些库文件(例如刚安装好glib),或者修改ld.so.conf增加新的库路径后,需要运行一下/sbin/ldconfig 使所有的库文件都被缓存到ld.so.cache中,不然修改的内容就等于没有生效。

(3)参数控制:

在编译程序的链接阶段,

除了上面两种设置链接库的搜索路径方式之外,还可以通过 -L 和 -l 参数显式指定。因为用 -L 设置的路径将被优先搜索,所以在链接的时候通常都会以这种方式直接指定要链接的库的路径。

例如:

#添加动态链接库搜索路径 gcc foo.c -L/home/xiaowp/lib -lfoo -o foo

#添加静态链接库搜索路径 gcc foo.c -L/home/xiaowp/lib -static -lfoo -o foo

但是如果没有-L参数,只有-I参数,例如:-lssl、-lpthread等,即没有通过 -L 参数指定链接库的搜索路径,就会按照下面的搜索顺序去查找:

<1>通过环境变量LD_LIBRARY_PATH指定动态库搜索路径

<2>配置文件/etc/ld.so.conf中指定的动态库搜索路径;

<3>默认的动态库搜索路径/lib;

<4>默认的动态库搜索路径/usr/lib。

 

3、链接库文件的搜索路径顺序(从上到下,依次执行)

(1) 编译目标代码时指定的动态库搜索路径,即 -L 参数后面设置的搜索路径;

(2)环境变量LD_LIBRARY_PATH指定的动态库搜索路径;

(3)配置文件 /etc/ld.so.conf 中指定的动态库搜索路径;

(4)默认的动态库搜索路径/lib;

(5)默认的动态库搜索路径/usr/lib;

(6)默认的动态库搜索路径 /usr/local/lib。

 

4、使用pkg-config来生成三方库的链接参数

现在很多新的开发包一般都使用 pkg-config 来生成链接参数,使用 pkg-config –list-all 命令可以列出所有支持的开发包。

//查看glib库的包名

$ pkg-config –list-all|grep glib* glib-2.0 GLib - C Utility Library

pkg-config的用法:pkg -config –libs –cflags

其中pkgName是三方库的包名。使用gcc编译的时候需要使用(``)符号括起来。

例如:编译glib应用程序

方法1:

gcc -o glist_test glist_test.c -I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include -L/usr/local/lib -lglib-2.0

方法2:

gcc pkg-config glib-2.0 --cflags --libs glib_test.c -o glib_test

##########

posted @ 2023-04-26 21:40  igoodful  阅读(533)  评论(1编辑  收藏  举报