源码学习初章-基础知识储备


文章目录

  • 学前准备
    • 源码地址
    • 引言
    • extern "C"
  • 宏定义
    • 平台宏
      • 跨平台宏
      • vstdio平台禁用警告宏
    • 连接、双层宏定义
    • 函数宏
      • 系统函数宏
      • 自定义函数宏
      • 多语句执行宏do while0
    • 普通宏定义
  • C的一些必备函数知识
    • 回调函数和函数指针
      • 回调函数wireshark-4.0.7源码例子
      • 函数指针wireshark4.0.7源码例子
  • 结构体和关键字
    • 结构体和联合体
      • 结构体 struct
      • 联合体 union
    • 关键字
      • static
      • extern
      • C++关键字 explicit
  • 标志位与位操作
    • & 、 |、<<、>>
    • 标志位(|加标志)(&减/检标志)
  • 代码习惯
    • if else
    • if else break
    • 函数定义
  • 其他内容后续补充


学前准备

源码地址

cJSON库地址
sqlite-github源码地址
sqlite官方网址
wireshark源码

引言

  • 引言参考的是sqlite和wireshark的部分代码,第二篇开始进入cJSON正式篇
  • 第一篇暂时不涉及github的源码,参考的是sqlite官网发布的shell.c sqlite3.h .c等文件进行简单的知识储备
  • 另外由于工作中多是linux环境所以把vstdio卸载了,但是为了在windows下学习sqlite源码所以我采用的是Qt5.14.1 MinGW32进行开发
  • 本文只会使用cpp的new和delete,其他语法将均为c的语法
  • 面向对象只是一种设计模式,如果你愿意,你可以用汇编来实现面向对象

extern “C”

  • 声明:
    这么做的目的是把c作为底层开发库,这样任何一个编程语言都可以调用c写的库了
  • 区别
    1、因为在 C 语言中,默认的函数调用约定是 C 调用约定(C calling convention),参数通过栈传递,由调用者清理栈。
    2、在 C++ 中,默认的函数调用约定是 C++ 调用约定(C++ calling convention),其实质是通过名称修饰(Name Mangling)来支持函数重载,参数传递方式和 C 调用约定类似,但由于函数名被修饰,因此 C++ 编译器能够识别重载的函数
  • 做法
    在头文件中 所有的 函数 结构体 变量 等能想到的所有内容都放在两个 "{}"中
#ifdef __cplusplus
extern "C" {
void fun1();
void fun2();
static struct Stu{
	int a;
	char b;
}stu{0,0};
int a=10;
}
#endif

宏定义

平台宏

跨平台宏

#if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include 
#elif define(__linux__)
#include 
#endif

总结:
- !defined(_CRT_SECURE_NO_WARNINGS)是为了防止C4996报错
- defined(_WIN32)和defined(WIN32)用来判断是否是windows,defined(WIN64)是在win系统基础上判断是不是win64位

vstdio平台禁用警告宏

#if defined(_MSC_VER)
#pragma warning(disable : 4054)// 禁用类型转换警告。此警告提示可能存在的类型转换问题。
#pragma warning(disable : 4055)//禁用类型转换警告。此警告提示可能存在的类型转换问题。
#pragma warning(disable : 4100)//禁用未使用的形参警告。此警告提示声明了但未在函数体内使用的形式参数。
//等
#endif /* defined(_MSC_VER) */

连接、双层宏定义

#include 

// 定义连接运算符宏
#define CONCAT(x, y) x##y

//双层宏定义
/*
 * 双重宏的第一个宏用于接收参数,并进行预处理操作。
 * 第二个宏则负责将经过预处理的参数传递给其他宏或函数,或者用于宏展开后的标识符构造
 * */
// 第一个宏,用于将宏参数转换为字符串常量
#define SHELL_STRINGIFY_(f) #f
// 第二个宏,用于传递参数给第一个宏进行处理
#define SHELL_STRINGIFY(f) SHELL_STRINGIFY_(f)

int main() {
    // 使用连接运算符宏
    int CONCAT(number, 1) = 42; // 等同于 int number1 = 42;
    printf("Value: %d\n", number1);
    // 注意:下面这个例子在预处理阶段将连接运算符宏展开为"Hello",而不是一个新的标识符
    printf("%s\n", "Hello" "World");
    printf("The value is: %s\n", SHELL_STRINGIFY(number1));//使用双层宏定义
    return 0;
}

源码学习初章-基础知识储备_第1张图片

函数宏

系统函数宏

将系统函数变成我们自己的宏即可省去后期我们大量调用时的麻烦
当然也可以自己写个函数通过宏实现

# define GETPID (int)GetCurrentProcessId//获取当前进程函数
#include 
#include 

// 定义宏,用于获取当前进程ID
#define GETPID (int)GetCurrentProcessId

// 定义宏,用于检查给定的字符是否为空白字符(包括空格、制表符、换行符等)
#define IsSpace(X)  isspace((unsigned char)X)

// 定义宏,用于检查给定的字符是否为十进制数字字符(0-9)
#define IsDigit(X)  isdigit((unsigned char)X)

// 定义宏,用于将给定的字符转换为小写字符
#define ToLower(X)  (char)tolower((unsigned char)X)

// 定义宏,用于将给定的字符转换为大写字符
#define ToUpper(X)  (char)toupper((unsigned char)X)

int main() {
    // 使用宏来获取当前进程ID
    int pid = GETPID();

    // 输出当前进程ID
    printf("Current process ID: %d\n", pid);

    // 另外,我们也可以直接调用 GetCurrentProcessId 函数来获取进程ID
    int pid_direct = GetCurrentProcessId();
    printf("Current process ID (direct call): %d\n", pid_direct);

    // 测试字符处理的宏
    char ch = 'a';

    // 使用 IsSpace 宏来检查字符 ch 是否为空白字符
    if (IsSpace(ch)) {
        printf("The character is a whitespace character.\n");
    } else {
        printf("The character is not a whitespace character.\n");
    }

    // 使用 IsDigit 宏来检查字符 ch 是否为数字字符
    if (IsDigit(ch)) {
        printf("The character is a digit.\n");
    } else {
        printf("The character is not a digit.\n");
    }

    // 使用 ToLower 宏将字符 ch 转换为小写形式,并输出转换后的字符
    printf("The lowercase of the character is: %c\n", ToLower(ch));

    // 使用 ToUpper 宏将字符 ch 转换为大写形式,并输出转换后的字符
    printf("The uppercase of the character is: %c\n", ToUpper(ch));

    return 0;
}

源码学习初章-基础知识储备_第2张图片

自定义函数宏

//宏定义自己的函数
//注意显示转换不是必须的,但写上更加直观
#include 
using namespace std;

#define MIN(x,y) (int)min((int)x, (int)y)
int min(int a,int b) {
    int ret = a<b?a:b;
    cout<<"min is "<<ret<<endl;
    return ret;
}

int main()
{
   cout<<MIN(10,5);

   return 0;
}

在这里插入图片描述

多语句执行宏do while0

# define CONTINUE_PROMPT_RESET \
  do {setLexemeOpen(&dynPrompt,0,0); trackParenLevel(&dynPrompt,0);} while(0)
  • 当有多条语句要执行时需要用{}将函数括起来,而用{}需要用do while循环好处:
    1、使宏的用法更像函数调用,增加了代码的可读性和可维护性
    2、可以避免一些编译器警告,因为使用了多条语句,编译器会认为这是一个复合语句
    3、do-while(0) 循环体会执行一次,因为条件永远为真。这并不会引起运行时额外的开销,因为优化后的编译器会将其优化掉
  • 重点:宏中需要一行写完,如果分行需要用 \ 进行换行
#include 
// 假设这是全局变量,实际情况下可能是其他类型的全局变量
int dynPrompt;

// 假设这是虚拟的函数,实际情况下可能是其他具体的函数
void setLexemeOpen(int* prompt, int arg1, int arg2) {
    // 这里省略函数的具体实现
    dynPrompt = -1;
    printf("setLexemeOpen called with arguments: %d, %d\n", arg1, arg2);
}

void trackParenLevel(int* prompt, int arg) {
    // 这里省略函数的具体实现
    printf("trackParenLevel called with argument: %d\n", arg);
}

// 定义宏
#define CONTINUE_PROMPT_RESET \
    do { \
        setLexemeOpen(&dynPrompt, 0, 0); \
        trackParenLevel(&dynPrompt, 0); \
    } while(0)

int main() {
    printf("Before calling CONTINUE_PROMPT_RESET:\n");
    printf("dynPrompt = %d\n", dynPrompt);

    // 使用宏 CONTINUE_PROMPT_RESET
    CONTINUE_PROMPT_RESET;

    printf("\nAfter calling CONTINUE_PROMPT_RESET:\n");
    printf("dynPrompt = %d\n", dynPrompt);

    return 0;
}

源码学习初章-基础知识储备_第3张图片

# define CONTINUE_PROMPT_AWAITS(p,s) \
  if(p && stdin_is_interactive) setLexemeOpen(p, s, 0)
  //这个 if 是条件语句(单个条件语句),所以不用 do while0

普通宏定义

  • 宏代表的是指针
    #define CONTINUE_PROMPT_PSTATE_PTR (&dynPrompt)
  • 宏代表的是变量
    #define CONTINUE_PROMPT_PSTATE_VALUE dynPrompt
  • 代表的是具体的常数
    #define CONTINUE_PROMPT_PSTATE_CONSTANT 42
#include 

// 假设这是全局变量,实际情况下可能是其他类型的全局变量
int dynPrompt = 42;

// 定义三个不同的宏
#define CONTINUE_PROMPT_PSTATE_PTR (&dynPrompt)
#define CONTINUE_PROMPT_PSTATE_VALUE dynPrompt
#define CONTINUE_PROMPT_PSTATE_CONSTANT 42

int main() {
    int *ptr = CONTINUE_PROMPT_PSTATE_PTR; // ptr 指向 dynPrompt 的地址
    int value = CONTINUE_PROMPT_PSTATE_VALUE; // value 的值是 dynPrompt 的值,即 42
    int constant = CONTINUE_PROMPT_PSTATE_CONSTANT; // constant 的值是 42

    printf("dynPrompt: %d\n", dynPrompt);
    printf("Address of dynPrompt: %p\n", &dynPrompt);
    printf("ptr: %p\n", ptr);
    printf("value: %d\n", value);
    printf("constant: %d\n", constant);

    return 0;
}

源码学习初章-基础知识储备_第4张图片

C的一些必备函数知识

回调函数和函数指针

#include 
using namespace std;
//函数指针
void (*funPtr)(int a,int b);
//下面的 max和min会赋值给函数指针
void max(int a,int b){
    int ret = a>b?a:b;
    cout<<"max is "<<ret<<endl;
}
void min(int a,int b) {
    int ret = a<b?a:b;
    cout<<"min is "<<ret<<endl;
}
//回调函数
typedef void (*callBack)(int a,int b);//回调函数比函数指针多了一个typedef
void sameFun(int a, int b , callBack fun){
    fun(a,b);
}
int main()
{
   cout << "Hello World" << endl;
   funPtr = max;
   funPtr(10,5);
   funPtr = min;
   funPtr(10,5);

    sameFun(10,5,max);
    sameFun(10,5,min);
   return 0;
}

源码学习初章-基础知识储备_第5张图片

回调函数wireshark-4.0.7源码例子

packet.h:76
typedef int (*dissector_t)(tvbuff_t *, packet_info *, proto_tree *, void *);//回调函数声明

packet.c:3310
/* Register a new dissector by name. */
dissector_handle_t
register_dissector(const char *name, dissector_t dissector, const int proto)
{
	struct dissector_handle *handle;

	handle = new_dissector_handle(DISSECTOR_TYPE_SIMPLE, dissector, proto, name, NULL, NULL);

	return register_dissector_handle(name, handle);
}

packet-ssh.c:4799
 ssh_handle = register_dissector("ssh", dissect_ssh, proto_ssh);

packet-ssh.c:676
 static int
dissect_ssh(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
/*这里面是ssh解析的具体代码,不做展示*/
}

函数指针wireshark4.0.7源码例子

packet-ssh.c:185
truct ssh_flow_data {
    guint   version;

    gchar*  kex;
    int   (*kex_specific_dissector)(guint8 msg_code, tvbuff_t *tvb,
            packet_info *pinfo, int offset, proto_tree *tree,
            struct ssh_flow_data *global_data, guint *seq_num);//函数指针声明

    /* [0] is client's, [1] is server's */
#define CLIENT_PEER_DATA 0
#define SERVER_PEER_DATA 1
/*后续代码略*/
}
//---------------------------------------------------------------------------------
packet-ssh.c:1702
static void ssh_set_kex_specific_dissector(struct ssh_flow_data *global_data)
{
    const char *kex_name = global_data->kex;

    if (!kex_name) return;

    if (strcmp(kex_name, "diffie-hellman-group-exchange-sha1") == 0 ||
        strcmp(kex_name, "diffie-hellman-group-exchange-sha256") == 0)
    {
        global_data->kex_specific_dissector = ssh_dissect_kex_dh_gex;//函数指针赋值
    }
    /*dh group 和 ecdh 略*/
}
//---------------------------------------------------------------------------------
packet-ssh.c:1361
static int ssh_dissect_kex_dh_gex(guint8 msg_code, tvbuff_t *tvb,
        packet_info *pinfo, int offset, proto_tree *tree,
        struct ssh_flow_data *global_data, guint *seq_num)

结构体和关键字

结构体和联合体

结构体 struct

static struct DynaPrompt {
  char dynamicPrompt[PROMPT_LEN_MAX];
  char acAwait[2];
  int inParenLevel;
  char *zScannerAwaits;
} dynPrompt = { {0}, {0}, 0, 0 };
  • 因为C没有构造函数,不能像cpp一样在构造函数对结构体进行赋初值,因此它赋初值的方法是在结构体定义的尾部赋值

  • 每个成员变量含义:
    dynamicPrompt 数组被初始化为 {0}:即所有元素都是空字符 ‘\0’。
    acAwait 数组被初始化为{0}:即设置为两个空字符 ‘\0’。
    inParenLevel 被初始化为 0:表示括号的层级初始值为 0。
    zScannerAwaits被初始化为 0:它被初始化为 0,即指向空地址。

联合体 union

  • 联合体的所有成员共用同一块内存空间,不同成员在内存中的位置是重叠的。
  • 联合体的大小等于所有成员中占用内存最大的那个成员的大小
  • 在同一时间,只能使用一个成员,存储一个成员的值会覆盖其他成员的值。
#include 
#include 

union Data {
    int integerData;
    float floatData;
    char stringData[20];
} data;

int main() {
    // 存储整数数据
    data.integerData = 42;
    printf("Integer Data: %d\n", data.integerData);

    // 存储浮点数数据
    data.floatData = 3.14;
    printf("Float Data: %.2f\n", data.floatData);

    // 存储字符串数据
    strcpy(data.stringData, "Hello, Union!");
    printf("String Data: %s\n", data.stringData);

    // 访问整数数据(此时整数数据会被覆盖)
    printf("Integer Data: %d\n", data.integerData);

    return 0;
}

源码学习初章-基础知识储备_第6张图片

关键字

  • static和extern的作用是相反的

static

因为源码中有大量的static函数、结构体等。
静态函数的作用是:

  • 函数隐藏在当前源文件中,对其他部分不可见,实现隐藏实现细节的目的
  • 将函数声明为 static 可以限制其作用域,避免与其他文件中的同名函数冲突
  • 这是一种常见的编程技巧,用于实现模块化的代码结构和增强程序的安全性。

extern

  • 当在一个源文件中使用 extern 来声明函数时,它告诉编译器该函数是在另一个源文件中定义的,而不是在当前文件中定义的
  • 通常,这样的声明会放在头文件中,然后在其他源文件中包含该头文件,以便其他源文件可以访问和调用声明的函数。

C++关键字 explicit

  • 这个关键字加在构造函数前,保证代码安全性
class MyClass {
public:
    explicit MyClass(int value) {
        // 构造函数的实现
    }
};
//如果没有explicit关键字那么构造函数可以隐式调用
int num = 42;
MyClass obj = num; // 隐式调用构造函数,将整数转换为 MyClass 类型
//加上explicit关键字必须显式创建对象
int num = 42;
MyClass obj(num); // 显式调用构造函数,将整数转换为 MyClass 类型

标志位与位操作

& 、 |、<<、>>

1000&1010 = 1000
1000|1010 = 1010
1<<8 = 1*2的八次方(10000000)
256>>8 = 256/2的八次方(
10000000>>8)
可以简单地理解为加减法的关系,本处不做具体解释,可以参考网上其他信息

标志位(|加标志)(&减/检标志)

#include 

// 定义标志位的常量
#define FLAG_A (1 << 0) // 00000001
#define FLAG_B (1 << 1) // 00000010
#define FLAG_C (1 << 2) // 00000100

int main() {
    int flags = 0; // 初始标志位为 0

    // 设置标志位
    flags |= FLAG_A; // 将 FLAG_A 设置为 1
    flags |= FLAG_C; // 将 FLAG_C 设置为 1

    // 检查标志位
    if (flags & FLAG_A) {
        printf("标志位 A 已设置\n");
    } else {
        printf("标志位 A 未设置\n");
    }

    if (flags & FLAG_B) {
        printf("标志位 B 已设置\n");
    } else {
        printf("标志位 B 未设置\n");
    }

    if (flags & FLAG_C) {
        printf("标志位 C 已设置\n");
    } else {
        printf("标志位 C 未设置\n");
    }

    return 0;
}

过程

  • flags = 00000000
  • FLAG_A = 00000001
    flags = 00000000 | 00000001 = 00000001
  • FLAG_C = 00000100
    flags = 00000001 | 00000100 = 00000101
  • FLAG_A = 00000001
    flags = 00000101 & 00000001 = 00000001 (非零,表示标志位 A 已设置)
  • FLAG_B = 00000010
    flags = 00000101 & 00000010 = 00000000 (为零,表示标志位 B 未设置)
  • FLAG_C = 00000100
    flags = 00000101 & 00000100 = 00000100 (非零,表示标志位 C 已设置)
  • 这样我们通过 | 加标志 再用 & 检查标志,我们就可以通过3bit的数据获得8种标志位情况
  • 实际上我们不必用&检查标志位,因为8种情况我们都是知道的所以直接 == 0111(0x07) 或 0011(0x03)(等6种情况)即可
    注:各种源码这种标志位过于普遍,本处不再贴此类代码

代码习惯

if else

if( dynPrompt.inParenLevel>9 ){
  shell_strncpy(dynPrompt.dynamicPrompt, "(..", 4);
}else if( dynPrompt.inParenLevel<0 ){
  shell_strncpy(dynPrompt.dynamicPrompt, ")x!", 4);
}else{
  shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4);
  dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel);
}

if else break

if( nmb !=0 && noc+nmb <= ncmax ){
          //sss
}else break; 

函数定义

这种函数参数列表是第一次看到
工作中没有遇到过这种写法,但是这种注释确实比较方便,易读性很好
后续会对这种格式进行讨论

static char *shellFakeSchema(
  sqlite3 *db,            /* The database connection containing the vtab */
  const char *zSchema,    /* Schema of the database holding the vtab */
  const char *zName       /* The name of the virtual table */
){
//。。。
}

其他内容后续补充

这个是为了cJSON库源码学习先初步学习的一些知识储备

你可能感兴趣的:(cJSON源码学习,c++)