数码港 太原网站开发公司,iis 建立子网站,网站建设常态化工作机制,软件开发培训机构哪家好文章目录 学前准备源码地址引言extern C 宏定义平台宏跨平台宏vstdio平台禁用警告宏 连接、双层宏定义函数宏系统函数宏自定义函数宏多语句执行宏do while0 普通宏定义 C的一些必备函数知识回调函数和函数指针回调函数wireshark-4.0.7源码例子函数指针wireshark4.0… 文章目录 学前准备源码地址引言extern C 宏定义平台宏跨平台宏vstdio平台禁用警告宏 连接、双层宏定义函数宏系统函数宏自定义函数宏多语句执行宏do while0 普通宏定义 C的一些必备函数知识回调函数和函数指针回调函数wireshark-4.0.7源码例子函数指针wireshark4.0.7源码例子 结构体和关键字结构体和联合体结构体 struct联合体 union 关键字staticexternC关键字 explicit 标志位与位操作 、 |、、标志位(|加标志)(减/检标志) 代码习惯if elseif 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 {
#endif
void fun1();
void fun2();
static struct Stu{int a;char b;
}stu{0,0};
int a10;
#ifdef __cplusplus
}
#endif宏定义
平台宏
跨平台宏
#if (defined(_WIN32) || defined(WIN32)) !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include windows.h
#elif define(__linux__)
#include socket.h
#endif总结: - !defined(_CRT_SECURE_NO_WARNINGS)是为了防止C4996报错 - defined(_WIN32)和defined(WIN32)用来判断是否是windowsdefined(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 stdio.h// 定义连接运算符宏
#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;
} 函数宏
系统函数宏 将系统函数变成我们自己的宏即可省去后期我们大量调用时的麻烦 当然也可以自己写个函数通过宏实现 # define GETPID (int)GetCurrentProcessId//获取当前进程函数#include stdio.h
#include windows.h// 定义宏用于获取当前进程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() {// 使用宏来获取当前进程IDint pid GETPID();// 输出当前进程IDprintf(Current process ID: %d\n, pid);// 另外我们也可以直接调用 GetCurrentProcessId 函数来获取进程IDint 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;
} 自定义函数宏
//宏定义自己的函数
//注意显示转换不是必须的但写上更加直观
#include iostream
using namespace std;#define MIN(x,y) (int)min((int)x, (int)y)
int min(int a,int b) {int ret ab?a:b;coutmin is retendl;return ret;
}int main()
{coutMIN(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 stdio.h
// 假设这是全局变量实际情况下可能是其他类型的全局变量
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_RESETCONTINUE_PROMPT_RESET;printf(\nAfter calling CONTINUE_PROMPT_RESET:\n);printf(dynPrompt %d\n, dynPrompt);return 0;
} # 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 stdio.h// 假设这是全局变量实际情况下可能是其他类型的全局变量
int dynPrompt 42;// 定义三个不同的宏
#define CONTINUE_PROMPT_PSTATE_PTR (dynPrompt)
#define CONTINUE_PROMPT_PSTATE_VALUE dynPrompt
#define CONTINUE_PROMPT_PSTATE_CONSTANT 42int main() {int *ptr CONTINUE_PROMPT_PSTATE_PTR; // ptr 指向 dynPrompt 的地址int value CONTINUE_PROMPT_PSTATE_VALUE; // value 的值是 dynPrompt 的值即 42int constant CONTINUE_PROMPT_PSTATE_CONSTANT; // constant 的值是 42printf(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;
} C的一些必备函数知识
回调函数和函数指针
#include iostream
using namespace std;
//函数指针
void (*funPtr)(int a,int b);
//下面的 max和min会赋值给函数指针
void max(int a,int b){int ret ab?a:b;coutmax is retendl;
}
void min(int a,int b) {int ret ab?a:b;coutmin is retendl;
}
//回调函数
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;
} 回调函数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:4799ssh_handle register_dissector(ssh, dissect_ssh, proto_ssh);packet-ssh.c:676static 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 clients, [1] is servers */
#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 stdio.h
#include string.hunion 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;
} 关键字 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 类型 标志位与位操作 、 |、、 10001010 1000 1000|1010 1010 18 1*2的八次方10000000 2568 256/2的八次方( 100000008) 可以简单地理解为加减法的关系本处不做具体解释可以参考网上其他信息 标志位(|加标志)(减/检标志)
#include stdio.h// 定义标志位的常量
#define FLAG_A (1 0) // 00000001
#define FLAG_B (1 1) // 00000010
#define FLAG_C (1 2) // 00000100int main() {int flags 0; // 初始标志位为 0// 设置标志位flags | FLAG_A; // 将 FLAG_A 设置为 1flags | 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 00000000FLAG_A 00000001 flags 00000000 | 00000001 00000001FLAG_C 00000100 flags 00000001 | 00000100 00000101FLAG_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.inParenLevel9 ){shell_strncpy(dynPrompt.dynamicPrompt, (.., 4);
}else if( dynPrompt.inParenLevel0 ){shell_strncpy(dynPrompt.dynamicPrompt, )x!, 4);
}else{shell_strncpy(dynPrompt.dynamicPrompt, (x., 4);dynPrompt.dynamicPrompt[2] (char)(0dynPrompt.inParenLevel);
}if else break
if( nmb !0 nocnmb 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库源码学习先初步学习的一些知识储备