引言 - 一切才刚刚开始
structc 是 C 结构基础库. 简单可复用.
structc - https://github.com/wangzhione/structc
之前也描述过几次 structc, 文字多代码风格少. 最近加班不多, 准备详细解说哈其思考初衷.
0.0 整体结构
structc
├── extern
├── LICENSE
├── Makefile
├── README.md
├── structc
└── structc.sln
structc.sln : winds 项目管理文件 visual studio
structc : 项目整体源码和素材文件目录
README.md : 项目介绍 Markdown
Makefile : linux 编译文件 make
LICENSE : MIT 开源协议
extern : 项目引入的外部库目录
extern ├── jemalloc ├── jemalloc-vc141-Release-static.lib ├── libuv.lib ├── pthread.h ├── pthread_lib.lib ├── sched.h ├── semaphore.h ├── strings.h ├── uv └── uv.h
以上就是我们看到 structc 项目整体结构.
0.1 外部库
当前很谨慎的引入两个半外部库. 最大程度会静态库编译链接运行. 荣我慢慢细说.
1. jemalloc - https://github.com/jemalloc/jemalloc
jemalloc 是 c 构建底层高性能 malloc 库. 也被称为系统编程末期最后免费午餐. 整个 structc
malloc 全权交给 je_malloc 抗压. 其中 winds 编译静态库部分, 项目本身也有细说 -
https://github.com/jemalloc/jemalloc/tree/dev/msvc
How to build jemalloc for Windows ================================= 1. Install Cygwin with at least the following packages: * autoconf * autogen * gawk * grep * sed 2. Install Visual Studio 2015 or 2017 with Visual C++ 3. Add Cygwinin to the PATH environment variable 4. Open "x64 Native Tools Command Prompt for VS 2017" (note: x86/x64 doesn't matter at this point) 5. Generate header files: sh -c "CC=cl ./autogen.sh" 6. Now the project can be opened and built in Visual Studio: msvcjemalloc_vc2017.sln
( 注: vs 使用最新版本. 网址打不开那就翻墙. 后面其也一样, 时刻保证最新 2018/10/10 ~ )
对于 linux 编译安装参照下面脚本
# 开发环境安装 sudo apt install gcc gdb autogen autoconf # jemalloc 安装 cd wget https://github.com/jemalloc/jemalloc/releases/download/5.1.0/jemalloc-5.1.0.tar.bz2 tar -jxvf jemalloc-5.1.0.tar.bz2 cd jemalloc-5.1.0 sh autogen.sh make -j4 sudo make install sudo ldconfig cd rm -rf jemalloc-5.1.0 jemalloc-5.1.0.tar.bz2
当 jemalloc 构建好了. 设计 alloc 层引入到 structc 框架中, 用户取代系统 malloc...
alloc.h - https://github.com/wangzhione/structc/blob/master/structc/system/alloc.h
#ifndef _H_ALLOC #define _H_ALLOC #include <stdlib.h> #include <string.h> // :) 高效内存分配, 莫名伤感 ~ // _MSC_VER -> Winds CL // __GNUC__ -> Linux GCC // #ifdef _MSC_VER // // CPU 检测 x64 or x86 // ISX64 defined 表示 x64 否则 x86 // # if defined(_M_ARM64) || defined(_M_X64) # define ISX64 # endif // // _M_PPC 为 PowerPC 平台定义, 现在已不支持 // so winds 可以认为都是小端平台 // # if defined(_M_PPC) # define ISBENIAN # endif #elif __GNUC__ # if defined(__x86_64__) # define ISX64 # endif // // 大小端检测 : ISBENIAN defined 表示大端 // # if defined(__BIG_ENDIAN__) || defined(__BIG_ENDIAN_BITFIELD) # define ISBENIAN # endif #else # error BUILD ( ̄︶ ̄) S #endif // OFF_ALLOC - 关闭全局 free / malloc 配置 #ifndef OFF_ALLOC # undef free # define free free_ # undef strdup # define strdup strdup_ # undef malloc # define malloc malloc_ # undef calloc # define calloc calloc_ # undef realloc # define realloc realloc_ #endif//OFF_ALLOC // // free_ - free 包装函数 // ptr : 内存首地址 // return : void // extern void free_(void * ptr); // // malloc_ - malloc 包装, 封装一些特殊业务 // size : 分配的内存字节 // return : 返回可使用的内存地址. // extern void * malloc_(size_t size); // // strdup_ - strdup 包装函数 // s : ' ' 结尾 C 字符串 // return : 拷贝后新的 C 字符串 // extern char * strdup_(const char * s); // // calloc_ - calloc 包装, 封装一些特殊业务 // num : 数量 // size : 大小 // return : 返回可用内存地址, 并且置0 // extern void * calloc_(size_t num, size_t size); // // realloc_ - realoc 包装函数, 封装一些特殊业务 // ptr : 内存首地址, NULL 等同于 malloc // size : 重新分配的内存大小 // return : 返回重新分配好的新地址内容 // extern void * realloc_(void * ptr, size_t size); #endif//_H_STDEXIT
alloc.c - https://github.com/wangzhione/structc/blob/master/structc/system/alloc.c
#include <stdio.h> #define OFF_ALLOC #include "alloc.h" #define JEMALLOC_NO_DEMANGLE #include <jemalloc/jemalloc.h> // // free_ - free 包装函数 // ptr : 内存首地址 // return : void // inline void free_(void * ptr) { je_free(ptr); } // 简单内存不足检测处理 static inline void * mcheck(void * ptr, size_t size) { if (NULL == ptr) { fprintf(stderr, "out of memory trying to allocate %zu ", size); fflush(stderr); abort(); } return ptr; } // // malloc_ - malloc 包装, 封装一些特殊业务 // size : 分配的内存字节 // return : 返回可使用的内存地址. // inline void * malloc_(size_t size) { void * ptr = je_malloc(size); return mcheck(ptr, size); } // // strdup_ - strdup 包装函数 // s : ' ' 结尾 C 字符串 // return : 拷贝后新的 C 字符串 // inline char * strdup_(const char * s) { if (s) { size_t n = strlen(s) + 1; char * ptr = malloc_(n); return memcpy(ptr, s, n); } return NULL; } // // calloc_ - calloc 包装, 封装一些特殊业务 // num : 数量 // size : 大小 // return : 返回可用内存地址, 并且置0 // inline void * calloc_(size_t num, size_t size) { void * ptr = je_calloc(num, size); return mcheck(ptr, size); } // // realloc_ - realoc 包装函数, 封装一些特殊业务 // ptr : 内存首地址, NULL 等同于 malloc // size : 重新分配的内存大小 // return : 返回重新分配好的新地址内容 // inline void * realloc_(void * ptr, size_t size) { void * ntr = je_realloc(ptr, size); return mcheck(ntr, size); }
包装了一层. 从 alloc.h 中 OFF_ALLOC 宏可以看出, 具备支持插拔能力 ~
2. libuv - https://github.com/libuv/libuv
libuv 用 c 写的高性能单线程网络 io 库. 希望通过它来支撑网络层. winds 编译静态库
参照 libuv 项目首页燥起来就行. 其中 gyp 安装了这个版本, 其它随波逐流 ~
gyp - https://github.com/adblockplus/gyp
linux 编译安装脚本
# libuv 安装 cd wget https://github.com/libuv/libuv/archive/v1.23.1.zip unzip v1.23.1.zip cd libuv-1.23.1 sh autogen.sh ./configure make -j4 sudo make install sudo ldconfig cd # # 注意 uv 头文件, 全部导入到系统 include 目录下面 # rm -rf libuv-1.23.1 v1.23.1.zip
注意要将编译后 include 完整拷贝到安装目录 include下. 这样 uv 头文件全, 日后会用到.
libuv 开箱即用, 不太需要什么基础封装.
3. pthread - https://github.com/GerHobbelt/pthread-win32
这是最后那半个, 为 winds 引入 POSIX thread 模型. 编译起来很简单(前提咱们 VS 玩的熟).
扯点闲篇. linux 和 winds 相辅相成, 对立而统一. 一个是一切从头码, 一个开始就已经注册未来.
描述比较粗, 但大概这意思. (两个都不 eary, 玩很久才敢入门见岳父岳母) . 这里包装了一层
thread.h - https://github.com/wangzhione/structc/blob/master/structc/system/thread.h
#ifndef _H_THREAD #define _H_THREAD #include "struct.h" #include <pthread.h> #include <semaphore.h> // // pthread_end - 等待启动线程结束 // tid : 线程id // return : void // inline void pthread_end(pthread_t tid) { pthread_join(tid, NULL); } // // pthread_run - 异步启动线程 // id : &tid 线程id地址 // frun : 运行的主体 // arg : 运行参数 // return : 返回线程构建结果, 0 is success // #define pthread_run(id, frun, arg) pthread_run_(&(id), (node_f)(frun), (void *)(intptr_t)(arg)) inline int pthread_run_(pthread_t * id, node_f frun, void * arg) { return pthread_create(id, NULL, (start_f)frun, arg); } // // pthread_async - 异步启动分离线程 // frun : 运行的主体 // arg : 运行参数 // return : 返回 0 is success // #define pthread_async(frun, arg) pthread_async_((node_f)(frun), (void *)(intptr_t)(arg)) inline int pthread_async_(node_f frun, void * arg) { int ret; pthread_t tid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); ret = pthread_create(&tid, &attr, (start_f)frun, arg); pthread_attr_destroy(&attr); return ret; } #endif//_H_THREAD
利用现代编译器兼容性构建了 pthread 两种启动宏, 后续写 pthread create 相关代码会得心应手!
到此我们大一统治线程模型就定下来了. 还顺带引出了一个很重要辅助头文件.
struct.h - https://github.com/wangzhione/structc/blob/master/structc/struct/struct.h
#ifndef _H_STRUCT #define _H_STRUCT #include <math.h> #include "alloc.h" #include <ctype.h> #include <float.h> #include <stdio.h> #include <errno.h> #include <assert.h> #include <stdarg.h> #include <stdint.h> #include <stddef.h> #include <limits.h> #include <stdbool.h> #include <inttypes.h> // // enum Flag int - 函数返回值全局状态码 // >= 0 标识 Success 状态, < 0 标识 Error 状态 // enum { SBase = +0, // 正确基础类型 EBase = -1, // 错误基础类型 EParam = -2, // 输入参数错误 EFd = -3, // 文件打开失败 EClose = -4, // 文件操作关闭 EAccess = -5, // 没有操作权限 EAlloc = -6, // 内存操作错误 EParse = -7, // 协议解析错误 ESmall = -8, // 过小基础错误 EBig = -9, // 过大基础错误 ETimeout = -10, // 操作超时错误 }; // // DCODE - DEBUG 模式下的测试宏 // DCODE({ // puts("debug start..."); // }); // #ifndef DCODE # ifdef _DEBUG # define DCODE(code) do code while(0) # else # define DCODE(code) # endif // ! _DEBUG #endif // ! DCODE // // icmp_f - 比较行为的类型 // : int add_cmp(const void * now, const void * node) // typedef int (* icmp_f)(); // // vnew_f - 根据规则构建对象 // : void * rtree_new(void * node) // typedef void * (* vnew_f)(); // // node_f - 销毁当前对象节点 // : void list_die(void * node); // typedef void (* node_f)(void * node); // // start_f - pthread create func // : int * run(int * arg) // typedef void * (* start_f)(void * arg); // // each_f - each 循环操作, arg 外部参数, node 是内部结点 // : int dict_echo(struct dict * node, void * arg) { return 0; } // typedef int (* each_f)(void * node, void * arg); // // CERR - 打印错误信息 // EXIT - 打印错误信息, 并 exit // IF - 条件判断异常退出的辅助宏 // #define CERR(fmt, ...) fprintf(stderr, "[%s:%s:%d][%d:%s]" fmt " ", __FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__) #define EXIT(fmt, ...) do { CERR(fmt, ##__VA_ARGS__); exit(EXIT_FAILURE); } while(0) #define IF(cond) if ((cond)) EXIT(#cond) // // RETURN - 打印错误信息, 并 return 返回指定结果 // val : return的东西, 当需要 return void; 时候填 ',' 就过 or NIL // fmt : 双引号包裹的格式化字符串 // ... : fmt中对应的参数 // return : val // #define RETURN(val, fmt, ...) do { CERR(fmt, ##__VA_ARGS__); return val; } while(0) #define NIL #define RETNIL(fmt, ...) RETURN(NIL , fmt, ##__VA_ARGS__) #define RETNUL(fmt, ...) RETURN(NULL, fmt, ##__VA_ARGS__) #endif//_H_STRUCT
作者尝试写 structc 项目时第一个源文件 : )
0.2 IDE 弱议
winds 没得选, 最新最全的 visual studio best version 有才能统治一切. 这里主要说
的是 linux 上面我们的选择. 最开始我是 vi + make + gcc + gdb 开发和编译的.
Makefile - https://github.com/wangzhione/structc/blob/master/Makefile
# 编译的目录结构 # Release : make # Debug : make D=-D_DEBUG # Clean : make clean
make 是编译发布, make D=-D_DEBUG 是编译 Debug, make clean 项目清理. 手工操作.
这样搞对我都还好, 什么都行.
但不妨更精进一步 [vi + make + gcc + gdb] -> [code + F5 + F10 + F11] 是不是更妙.
微软作为桌面软件霸主, code(VSCode 简称)不用我多说, 不得不服. 那开搞
1. 安装软件
ubuntu best version
vscode
安装好 vscode 后, 在其内部安装插件 Microsoft C/C++ for Visual Studio Code
2. F1 -> Edit Configurations -> c_cpp_properties.json
设置如下内容和VS配置很相似
{ "configurations": [ { "name": "Linux", "includePath": [ "/usr/include/c++/7", "/usr/include/x86_64-linux-gnu/c++/7", "/usr/include/c++/7/backward", "/usr/lib/gcc/x86_64-linux-gnu/7/include", "/usr/local/include", "/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed", "/usr/include/x86_64-linux-gnu", "/usr/include", "${workspaceRoot}", "${workspaceRoot}/structc/base", "${workspaceRoot}/structc/struct", "${workspaceRoot}/structc/system" ], "defines": [ "_DEBUG", "__GNUC__" ], "intelliSenseMode": "clang-x64", "browse": { "path": [ "/usr/include/c++/7", "/usr/include/x86_64-linux-gnu/c++/7", "/usr/include/c++/7/backward", "/usr/lib/gcc/x86_64-linux-gnu/7/include", "/usr/local/include", "/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed", "/usr/include/x86_64-linux-gnu", "/usr/include", "${workspaceRoot}" ], "limitSymbolsToIncludedHeaders": true, "databaseFilename": "" }, "compilerPath": "/usr/bin/clang", "cStandard": "c11", "cppStandard": "c++17" } ], "version": 4 }
3. F5 -> launch.json
{ // 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/Out/main.exe", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "preLaunchTask": "Debug", "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] }
4. F5 -> tasks.json
{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "type" : "shell", "label" : "Debug", "command" : "make D=-D_DEBUG" } ] }
此刻我们就可以 F5 搞起来 ~
兄弟们是不是很亲切, 这么复杂定制化项目都可以可视化调试. 还有谁 ~ 当然 IDE 有没有
都好说, 难说的是你是否耐的下心去感悟技术的脉络, 可不能学京东技术, 对开源缺失敬畏
之心, 技术不见得多厉害, 节操提前贷款没了 ~ 最终成为奥义之梗 : )
前言 - 不妨说点设计
进入 structc/structc 看到以下项目结构
wzhi@wzc:~/structc/structc$ tree -L 1 . ├── base ├── conf ├── main ├── README.md ├── struct ├── structc.vcxproj ├── structc.vcxproj.filters ├── structc.vcxproj.user ├── system └── test
base : 基础接口封装目录
conf : 配置文件目录
main : 主函数目录
struct : 数据结构接口目录
system : 系统库包装目录
test : 单元测试目录
1.0 main 主函数设计
wzhi@wzc:~/structc/structc/main$ tree
.
├── main.c
├── main_init.c
├── main_run.c
└── main_test.c
重点关注下入口 mian 主函数设计 main.c
#include "head.h" // // main - 程序的总入口, 从扯开始 // argc : 输入参数个数 // argv : 参数集 // return : 返回程序退出的状态码 // int main(int argc, char * argv[]) { // // 初始化 ... ... // ... ... EXTERN_RUN(main_init); // // make D=-D_DEBUG // main_test 单元测试才会启动 // #ifdef _DEBUG EXTERN_RUN(main_test); #endif // ... // ... 启动当前项目运行的主函数 // EXTERN_RUN(main_run, argc, argv); return EXIT_SUCCESS; }
其中 EXTERN_RUN 也很奇巧
// // EXTERN_RUN - 简单的声明, 并立即使用的宏 // ftest : 需要执行的函数名称 // ... : 可变参数, 保留 // #define EXTERN_RUN(ftest, ...) do { extern void ftest(); ftest (__VA_ARGS__); } while(0)
越过声明直接使用的宏声明. structc 中 main 函数一共做了二件半事情.
main_init 初始化函数, main_run 业务运行函数, 还有半个 main_test 运行单元测试.
随后我们好好看看这个单元测试套路.
1.1 test 单元测试套路设计
先看看 main_test.c
#include "head.h" // // TEST - 用于单元测试函数, 执行并输出运行时间 // ftest : 需要执行的测试函数名称 // ... : 可变参数, 保留 // #define TEST(ftest, ...) do { extern void ftest(); clock_t $s = clock(); ftest (##__VA_ARGS__); double $e = (double)clock(); printf(STR(ftest)" run time:%lfs ", ($e-$s)/CLOCKS_PER_SEC); } while(0) // // main_test - *_test is here run // return : void // void main_test(void) { // // 开始你的表演, 单元测试 // EXTERN_RUN(uv_tty_test); }
以上只给予了业务测试的能力. 其中 uv_tty_test 函数就是单元测试目录下其中一个的单元测试函数体.
而我们每个业务测试函数, 顺带会创建一个同名的 .c 文件. 例如这里是 uv_tty_test.c
#include <uv.h> #include <stdio.h> #include <stdlib.h> #include <string.h> // // 测试 libuv tty 操作控制台 // 输出一段有颜色的文字 // void uv_tty_test(void) { uv_tty_t tty; uv_buf_t buf[3]; unsigned i, len = sizeof buf / sizeof *buf; uv_loop_t * loop = uv_default_loop(); // 目前只对 tty 控制台处理 if (uv_guess_handle(1) != UV_TTY) { fprintf(stderr, "uv_guess_handle(1) != UV_TTY! "); exit(EXIT_FAILURE); } uv_tty_init(loop, &tty, 1, 0); uv_tty_set_mode(&tty, UV_TTY_MODE_NORMAL); // 开始发送消息 buf[0].base = "