河南省建设协会网站,网站交互效果,自媒体wordpress主题分享,wordpress做的好的网站这次的内容涉及结构体内存布局、文件读写封装、终端进度条、core dump 调试这几块#xff0c;都是平时项目里高频用到的东西。废话不多说#xff0c;直接上菜。本文代码均基于 Linux GCC 环境验证。终端进度条先来个有意思的——终端进度条。做 OTA 升级、固件烧写、批量文件…这次的内容涉及结构体内存布局、文件读写封装、终端进度条、core dump 调试这几块都是平时项目里高频用到的东西。废话不多说直接上菜。本文代码均基于 Linux GCC 环境验证。终端进度条先来个有意思的——终端进度条。做 OTA 升级、固件烧写、批量文件拷贝的时候光看日志刷屏心里没底加个进度条一目了然。效果先看是不是还挺像回事实现起来其实也不复杂核心就是\r回车符不换行配合fflush刷新输出缓冲区来实现同行刷新。代码#include stdio.h #include string.h #include unistd.h typedefstruct _progress { int cur_size; int sum_size; }progress_t; void progress_bar(progress_t *progress_data) { int percentage 0; int cnt 0; char proc[102]; // 100个字符位 最后一个# \0 memset(proc, \0, sizeof(proc)); percentage (int)((longlong)progress_data-cur_size * 100 / progress_data-sum_size); printf(percentage %d %%\n, percentage); if (percentage 100) { while (cnt percentage) { printf([%-100s] [%d%%]\r, proc, cnt); fflush(stdout); proc[cnt] #; usleep(100000); cnt; } } printf(\n); } int main(int arc, char *argv[]) { progress_t progress_test {0}; progress_test.cur_size 65; progress_test.sum_size 100; progress_bar(progress_test); return0; }运行结果快速获取结构体成员大小及偏移量搞嵌入式的应该都知道结构体的内存布局、对齐方式这些细节经常要关注——通信协议解析、共享内存操作的时候一不注意就踩坑。获取结构体成员偏移量标准库stddef.h里有个offsetof宏可以用不过我们也可以自己实现一版顺便搞清楚它背后的原理。思路很简单把 0 地址强转成结构体指针再去取成员的地址这个地址的值就是偏移量——因为结构体基地址是 0 嘛。获取成员大小同理对这个虚拟成员做 sizeof 就行。代码#include stdio.h #define GET_MEMBER_SIZE(type, member) sizeof(((type*)0)-member) #define GET_MEMBER_OFFSET(type, member) ((size_t)((((type*)0)-member))) typedefstruct _test_struct0 { char x; char y; char z; }test_struct0; typedefstruct _test_struct1 { char a; char c; short b; int d; test_struct0 e; }test_struct1; int main(int arc, char *argv[]) { printf(GET_MEMBER_SIZE(test_struct1, a) %zu\n, GET_MEMBER_SIZE(test_struct1, a)); printf(GET_MEMBER_SIZE(test_struct1, c) %zu\n, GET_MEMBER_SIZE(test_struct1, c)); printf(GET_MEMBER_SIZE(test_struct1, b) %zu\n, GET_MEMBER_SIZE(test_struct1, b)); printf(GET_MEMBER_SIZE(test_struct1, d) %zu\n, GET_MEMBER_SIZE(test_struct1, d)); printf(GET_MEMBER_SIZE(test_struct1, e) %zu\n, GET_MEMBER_SIZE(test_struct1, e)); printf(test_struct1 size %zu\n, sizeof(test_struct1)); printf(GET_MEMBER_OFFSET(a): %zu\n, GET_MEMBER_OFFSET(test_struct1, a)); printf(GET_MEMBER_OFFSET(c): %zu\n, GET_MEMBER_OFFSET(test_struct1, c)); printf(GET_MEMBER_OFFSET(b): %zu\n, GET_MEMBER_OFFSET(test_struct1, b)); printf(GET_MEMBER_OFFSET(d): %zu\n, GET_MEMBER_OFFSET(test_struct1, d)); printf(GET_MEMBER_OFFSET(e): %zu\n, GET_MEMBER_OFFSET(test_struct1, e)); return0; }运行结果跑出来的结果可以留意一下偏移量——b的偏移是 2 而不是紧挨着c后面的 112呃这里恰好对上了但d的偏移是 4这就是内存对齐在起作用。编译器为了让 int 对齐到 4 字节边界会在b后面自动填充。搞通信协议的时候不注意这个对端解析大概率就乱套了。文件操作封装文件读写的代码我们几乎每个项目都要写什么配置参数存储啊、日志落盘啊、固件数据读写啊太常见了。与其每次都重新写一遍 fopen/fwrite/fclose 那套不如封装两个通用函数拿来直接用代码#include stdio.h static int file_opt_write(const char *filename, void *ptr, int size) { FILE *fp; size_t num; fp fopen(filename, wb); if (NULL fp) { printf(open %s file error!\n, filename); return-1; } num fwrite(ptr, 1, size, fp); if (num ! size) { fclose(fp); printf(write %s file error!\n, filename); return-1; } fclose(fp); return (int)num; } static int file_opt_read(const char *filename, void *ptr, int size) { FILE *fp; size_t num; fp fopen(filename, rb); if (NULL fp) { printf(open %s file error!\n, filename); return-1; } num fread(ptr, 1, size, fp); if (num ! size) { fclose(fp); printf(read %s file error!\n, filename); return-1; } fclose(fp); return (int)num; } typedefstruct _test_struct { char a; char c; short b; int d; }test_struct; #define FILE_NAME ./test_file int main(int arc, char *argv[]) { test_struct write_data {0}; write_data.a 1; write_data.b 2; write_data.c 3; write_data.d 4; printf(write_data.a %d\n, write_data.a); printf(write_data.b %d\n, write_data.b); printf(write_data.c %d\n, write_data.c); printf(write_data.d %d\n, write_data.d); file_opt_write(FILE_NAME, (test_struct*)write_data, sizeof(test_struct)); test_struct read_data {0}; file_opt_read(FILE_NAME, (test_struct*)read_data, sizeof(test_struct)); printf(read_data.a %d\n, read_data.a); printf(read_data.b %d\n, read_data.b); printf(read_data.c %d\n, read_data.c); printf(read_data.d %d\n, read_data.d); return0; }这里用wb/rb模式打开文件就是二进制读写跟w/r文本模式的区别在于不会对换行符做转换。写结构体数据用二进制模式才是对的不然在 Windows 上可能会多出\r来。运行结果后台运行生成 core 文件程序跑着跑着突然挂了段错误、非法访问这类问题最头疼——出了现场就没了。这时候如果程序在崩溃时能自动吐出 core 文件事后再用 gdb 加载分析就能精准定位到哪行代码出的问题。代码#include stdio.h #include stdlib.h #include sys/time.h #include sys/resource.h #define SHELL_CMD_CONF_CORE_FILE echo /var/core-%e-%p-%t /proc/sys/kernel/core_pattern #define SHELL_CMD_DEL_CORE_FILE rm -f /var/core* static int enable_core_dump(void) { int resource RLIMIT_CORE; struct rlimit rlim; rlim.rlim_cur RLIM_INFINITY; rlim.rlim_max RLIM_INFINITY; system(SHELL_CMD_DEL_CORE_FILE); if (0 ! setrlimit(resource, rlim)) { printf(setrlimit error!\n); return-1; } system(SHELL_CMD_CONF_CORE_FILE); printf(core dump enabled, pattern: /var/core-%%e-%%p-%%t\n); return0; } int main(int argc, char **argv) { enable_core_dump(); printf(segmentation fault test\n); // 下面故意触发段错误仅为演示 core dump 功能 int *p NULL; *p 1234; return0; }core 文件生成后用gdb ./your_program /var/core-xxx加载bt命令看调用栈基本就能锁定崩溃位置了。好了以上就是本次分享的几个代码片段简单汇总一下代码片段适用场景终端进度条OTA 升级、固件烧写、批量操作结构体成员大小/偏移协议解析、内存布局分析文件读写封装配置存储、日志落盘、数据持久化core dump 使能崩溃后定位、事后调试这些代码片段我自己是一直在用的建议收藏起来丢到自己的工具库里用到的时候直接拿。你平时还有哪些压箱底的代码片段欢迎评论区聊聊说不定下一期就整理进来了。‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧ END ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧关注我的微信公众号回复“星球”加入知识星球有问必答。点击“阅读原文”查看知识星球详情欢迎点分享、收藏、点赞、在看。