C way to explore https:https://cdn.v2ex.com/navatar/72b3/2a1f/57_normal.png?m=1575483197 https:https://cdn.v2ex.com/navatar/72b3/2a1f/57_large.png?m=1575483197 2025-09-04T21:35:14Z Copyright © 2010-2018, V2EX 人再笨还能写不出内存安全的 C? tag:www.v2ex.com,2025-09-04:/t/1157154 2025-09-04T11:08:09Z 2025-09-04T21:35:14Z nlzy member/nlzy 人再笨还能学不会空指针不能解引用?

人再笨还能学不会超出存储期的对象的指针不能解引用?

人再笨还能学不会 alloc 出来的指针需要 free 一次?

人再笨还能学不会 alloc 出来的指针不能越界读取和写入?

人再笨还能学不会 malloc 出来的内存需要初始化才能读取?

人再笨还能学不会 free 只能用在 alloc 出来的指针上?

人再笨还能学不会 free'ed / realloc'ed 的指针不能再次 free ?

人再笨还能学不会重用 free'ed / realloc'ed 的存储是未定义行为?

人再笨还能学不会 realloc 返回 NULL 时原有指针仍需 free 一次?

人再笨还能学不会 memcpy memmove 的目标缓冲区的大小需要足够大?

人再笨还能学不会 memcpy 的两段内存区域不能重叠?

人再笨还能学不会 strcpy strlen puts printf("%s"...) 只能用在带 '\0' 终结符的字符串上?

...

]]>
想念 C tag:www.v2ex.com,2025-08-28:/t/1155444 2025-08-28T02:16:55Z 2025-08-29T04:25:15Z cj323 member/cj323 忍不住想起它

想起它骨子里的自由,放荡,甚至危险

我害怕却又兴奋

它没有边界感,但它的指针能带我去别人根本想象不了的环境

它给不了安全感,但它跑起来永远是最快最轻松的

它的世界里没有规矩,没有限制,更没有随波逐流的浮华

它能随时能做它想做的,要它想要的

C ,我想你了

]]>
C11 的 _Generic,现实当中用得多不多?(尤其是公司项目) tag:www.v2ex.com,2025-08-11:/t/1151667 2025-08-11T12:40:48Z 2025-08-11T13:39:48Z cnbatch member/cnbatch
https://www.reddit.com/r/C_Programming/s/liueGRtmXs

讨论当中有人提到 _Generic 或许挺有用,原 OP 认为 nullptr 比 NULL 更好。

C11 标准已经推出十几年了,这个 _Generic 用得多不多?毕竟 C23 引入 nullptr ,就是因为 _Generic 的缘故( N3042 提案)

然后原帖还有人觉得 _Generic 这个名字太糟糕了。我觉得也是,搞出好几个下划线开头的大小写混合关键字,用起来很不方便。

顺便一提,原 OP 的练手项目用的是 xmake 。
我记得 xmake 作者也在 V 站,给你一个大赞,你的项目又帮了一个人。 ]]> 坑爹的 GBK:大家都应该去用 UTF-8 tag:www.v2ex.com,2025-05-21:/t/1133223 2025-05-21T04:11:36Z 2025-05-25T04:56:02Z mikewang member/mikewang 最近在把我用 C 写的一批 Linux 工具移植到 Windows 上,在字符编码上遇到了大坑。


举个简单的例子:数文件层级。

在 Linux 上,我们数斜杠数量就好。

在 Windows 上,再加上反斜杠,应该就好了。——我是这样想的。

#include <stdio.h> int main(int argc, char *argv[]) { int level; const char *p; if (argc < 2) { return 1; } for (level = 0, p = argv[1]; *p; p++) { if (*p == '/' || *p == '\\') { level++; } } printf("%d\n", level); return 0; } 

用 MinGW 的 GCC 编译一下,然后跑几个用例:

gcc -o getlevel.exe getlevel.c 
C:\>getlevel C:\浙江省\宁波市\北仑区\小港街道.txt 4 C:\>getlevel C:\浙江省\宁波市\北仑区\大碶街道.txt 5 

天塌了,这么简单的代码竟然出了 bug 。


原来 的 编码是 {0xb4, 0x5c},其中 0x5c 和反斜杠的 ASCII 编码一模一样。

GBK 的第一字节兼容 ASCII ,但第二字节的范围是 0x40 ~ 0xfe,与 ASCII 的 0x00 ~ 0x7f 重叠。BUG 就这么诞生了。

UTF-8 没有这个问题的原因是:只要字节范围在 0x00 ~ 0x7f,那么就一定是 ASCII ,因为后续字节都避开了这个范围。虽然中文编码比 GB 系列长了,但是这个设计确实省了很多事。包括 strstr() strcmp() 之类的都不会出现奇奇怪怪的 bug 。


或许我应该使用 wmain() 然后获取 wchar_t,但是 wmain() 是 Windows 特有的东西,这样做就没法和 Linux 公用同一套代码了。目前加上了 mbtowc() 作为修复。原本简洁的代码变得十分复杂:(

说到这又不得不吐槽下 Windows 的各种奇怪 API 了,不知道它是如何存活到现在的...

]]>
分享一个用 AI 学习 C 语言的例子 tag:www.v2ex.com,2025-04-24:/t/1127752 2025-04-24T04:32:48Z 2025-04-24T10:06:51Z henix member/henix 以下来自我跟 DeepSeek 的对话:

我的提问

我要为 C 语言的 qsort 编写一个比较函数,比较两个 uint64_t ,考虑以下两种实现:

int qcmp_uint64(const void* pa, const void* pb) { uint64_t a = *(uint64_t*)pa; uint64_t b = *(uint64_t*)pb; if (a < b) return -1; if (a > b) return 1; return 0; } 
int qcmp_uint64_2(const void* pa, const void* pb) { uint64_t a = *(uint64_t*)pa; uint64_t b = *(uint64_t*)pb; return a - b; } 

从直觉上看,似乎第二种实现效率更高,但由于整数相减可能溢出,我对第二种方案的正确性产生了怀疑

第二种方案是否正确?如何高效实现这一比较函数?

DeepSeek 回答

对于第二种实现 qcmp_uint64_2,直接返回 a - b 的确存在隐患。原因如下:

  1. 无符号整数减法不会溢出,但结果始终为非负数。

    • 当 a < b 时,a - b 会得到一个极大的无符号值(模运算结果),例如 1 - 2 = UINT64_MAX 。
  2. 转换为 int 可能导致符号错误:

    • 若 a - b 的值超过 INT_MAX ,转换为 int 时会触发实现定义行为(通常为截断高位,导致结果为负数)。此时 qsort 会误判 a < b ,造成排序错误。

例如:

高效且安全的实现 利用 比较操作的布尔值隐式转换为 0/1 ,可写出无分支的高效代码:

int qcmp_uint64(const void* pa, const void* pb) { uint64_t a = *(const uint64_t*)pa; uint64_t b = *(const uint64_t*)pb; return (a > b) - (a < b); // 若 a > b 则 1-0=1 ; a < b 则 0-1=-1 ;相等则为 0 } 

优点:

性能验证

在 x86-64 架构下,上述代码的典型汇编输出为:

mov rax, [rdi] cmp [rsi], rax setb al seta cl movzx eax, al movzx ecx, cl sub eax, ecx ret 

完全无分支,效率与减法方案相当,且绝对安全。

结论:不要使用 a - b 方案,优先选择无分支的布尔比较实现。

点评

这个 (a > b) - (a < b) 的写法太巧妙了!

感觉这类惯用法( idiom )很难通过看书或网络学习,正是 AI 的强项

只搜到一个相关的: https://stackoverflow.com/questions/3886446/problem-trying-to-use-the-c-qsort-function

]]>
一个简单的 C 程序,但是不明白区别在哪里 tag:www.v2ex.com,2025-04-15:/t/1125556 2025-04-15T03:49:31Z 2025-04-15T15:57:38Z kelvinaltajiin member/kelvinaltajiin #include <stdio.h> void assign_value(int *array, int index, int value); int main() { printf("Hello, World!\n"); int array[10]; assign_value(array, 16, 131); printf("%d\n", array[16]); return 0; } void assign_value(int *array, int index, int value) { array[index] = value; printf("done\n"); }

编译:$ gcc -g -Wall -std=c18 -o hello_world hello_world.c 运行输出:

Hello, World! done 131 [1] 3719 segmentation fault (core dumped) ./hello_world 

但是如果把 index 从 16 改成 12, 则不会出现最后的 segmentation fault. 如果 C 不处理越界的话,为什么 16 会报错,如果处理越界为什么 12 不报错?

]]>
我这段 C 代码可以在编译时候输出结构体的大小,你们还有什么好点子, show me the code! tag:www.v2ex.com,2025-02-13:/t/1111103 2025-02-13T02:25:49Z 2025-02-13T06:23:49Z Exxfire member/Exxfire #include <stdio.h> struct X { int a; char c[10]; short d; }; char checker(int a) { (void)a; return '1'; } #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #define TEST(x) \ do \ { \ char checkSizeOfInt[sizeof(x)] = {checker(&checkSizeOfInt)}; \ _Pragma(TOSTRING(message("Value: " #x))) \ } while (0); int main() { TEST(struct X); return 0; } ]]> 这段话是否正确?「取余这个运算,只有 Python 是对的。当初 C 这个老师教错了,那么一大票学生也就只敢跟着老师错。只有 Python 敢于站出来坚持正确答案。」 tag:www.v2ex.com,2024-11-27:/t/1093235 2024-11-27T15:50:46Z 2024-11-28T14:08:05Z cnbatch member/cnbatch 今天在看一篇公众号文章《性能之王:最快的编程语言》,发现评论区有这么一段对话:

img

img2

然后我找了下在 stackexchange 的真实提问:

https://math.stackexchange.com/questions/623449/negative-number-divided-by-positive-number-what-would-be-remainder

从回答来看,C 和 Python 的两种做法在数值计算上都是成立的。两种做法的区别在于是否允许余数为负数,或者说,符号该不该与原数值相同。

不允许余数出现负数的,是目前广泛使用的欧几里得除法。

所以“数学洁癖”会认为负值余数是错的?

]]>
C 中可变参数如何直接传递到 printf() tag:www.v2ex.com,2024-11-12:/t/1088956 2024-11-12T09:41:03Z 2024-11-12T16:10:29Z XIVN1987 member/XIVN1987 用到一个库,,它有一个接口函数如下:

/** * @brief interface print format data * @param[in] fmt is the format data * @note none */ void ov2640_interface_debug_print(const char *const fmt, ...) { } 

这个函数的函数原型与 printf 完全一致,,所以我想像下面这样直接把它的参数传递给 printf ,,可是编译器报错,,请问应该怎么改??

void ov2640_interface_debug_print(const char *const fmt, ...) { printf(fmt, ...); } 
]]>
C 的内存打印实现 tag:www.v2ex.com,2024-08-30:/t/1069103 2024-08-30T09:27:37Z 2024-08-30T14:44:57Z BlackSnow member/BlackSnow 请教一个基础问题,实际执行的代码时是放入了虚拟内存中,这个内存的格式与物理内存一致吗,还是每个内存单元占一字节 如果是的话,那么下面两个问题能不能直观反映

  1. C 语言支持具体的虚拟内存打印吗 比如 我是否可以使用某种函数下面的 1001 的具体内存值,想验证一下 f3 0f 1e fa 是从寄存器放入了 4 个内存地址还是说只是用了 1000 的地址 如下代码
 1000: f3 0f 1e fa endbr64 1004: 48 83 ec 08 sub $0x8,%rsp 1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 <__gmon_start__@Base> 100f: 48 85 c0 test %rax,%rax 1012: 74 02 je 1016 <_init+0x16> 
  1. 如果验证正确的话,这里的内存是操作系统系统模拟的,因此内存单元长度固定是 1 字节对吗
]]>
函数能否实现透传不定长度参数,最终由 printf 打印 tag:www.v2ex.com,2024-08-27:/t/1068118 2024-08-27T05:15:23Z 2024-08-27T06:34:28Z smdbh member/smdbh 比如要封装一个打印函数,打印内容在最后,入口时 myprint1 ,后续时 myprint2 ,。。。,参数要如何写?

void myprint1(uint8_t *pdata, size_t len, char *fmt, ...) { myprint2(fmt,...); // 这里参数怎么写 } void mprint2(char *fmt, ...) { printf(fmt,...); // 如何调用 } 

谢谢

]]>
请大佬帮忙修改一份 elf 文件里的数值 tag:www.v2ex.com,2024-08-15:/t/1065314 2024-08-15T11:41:10Z 2024-08-16T02:22:26Z cjd6568358 member/cjd6568358 文件链接

https://wwdm.lanzouo.com/iiUCY27i6vsd ]]>
Linux 上 C 的程序遇到个异常退出问题,局部变量大小有限制?? tag:www.v2ex.com,2024-08-06:/t/1062935 2024-08-06T06:39:25Z 2024-08-06T08:43:23Z SS945 member/SS945 #include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/prctl.h>
#include <string.h>
#include <pthread.h>
#include <liblx_api/lx_api.h>


static void *client_check(void *arg)
{
printf("###### %s %d ######\n", __FUNCTION__, __LINE__);
#define SZ_BUF 32768 /* 32*1024 */
char post_data[SZ_BUF] = {0};
char enc_msg[SZ_BUF + 16] = {0};
unsigned char cipher_data[SZ_BUF] = {0};
unsigned char cipher_str[SZ_BUF] = {0};
unsigned char test[SZ_BUF] = {0};
strcpy((char *)cipher_data, "AAAAAAAAAAAAAAAAA");
strcpy((char *)cipher_str, "CCCCCCCCCCCCCCC");
strcpy((char *)enc_msg, "DDDDDDDDDDDDDDDD");
strcpy((char *)post_data, "EEEEEEEEEEEEEEE");
strcpy((char *)test, "FFFFFFFFFFFFFFFFF");
printf("###### %s %d ######\n", __FUNCTION__, __LINE__);

return 0;
}


int main(void)
{
pthread_t tidp;
printf("\n######## start #####\n");
test_service(); //工作正常
if (pthread_create(&tidp, NULL, client_check, NULL) != 0) //异常退出
{
LX_NGX_ERR("pthread_create error!\n");
}

printf("AAAAAAA hello world!!!\n");
return 0;
}


上面的程序如果正常在 main 里面跑就是好的,但一放到 pthread_create 里面用线程跑就不行,放在 x86 上就可以,但放在我的板子里板就不行,这是什么鬼???? ]]>
C 语言新手求助:如何在 vscode 中使用第三方库? tag:www.v2ex.com,2024-07-08:/t/1055651 2024-07-08T06:18:43Z 2024-07-08T14:38:47Z craiiz member/craiiz

背景是这样的:最近闲着报了个深圳人社的 c 语言培训班。 完了之后想要把之前自己写的 python 图片处理脚本用 C 写一遍。 然后就发现没法引用第三方库(也许是自己哪里做错了)。

想要达成的目的

  1. 在 vscode 中使用第三方库;
  2. 了解我的 .vscode 里面的配置文件是不是哪里写错了?

当前情况及无法解决的问题(已 google 和 gpt)

以使用第三方库 fmt 为例,按照下述操作之后,在测试文件 test.c 里面输入 #include <f vscode 能自动联想出#include <fmt/format.h> ,但是在 debug 或 run without debug 时终端会报错以下内容 :

#include errors detected. Please update your includePath. Squiggles are disabled for this translation unit (C:\Users\xxx\Desktop\C\test.c).

cannot open source file "cmath" (dependency of "C:\Users\xxx.vscode\extensions\ms-vscode.cpptools-1.20.5-win32-x64\bin\fmt\format.h"). Please run the 'Select IntelliSense Configuration...' command to locate your system headers.

fmt/format.h: No such file or directory

已经进行的操作

  1. gcc/gdb 都已经正常安装并且可以在终端正常使用; 其中,gcc 路径为:C:\MinGW\bin\gcc.exe ; gdb 版本为 GNU gdb (GDB) 7.6.1 ;

  2. 包管理用的是 vcpkg; 安装到包的路径是:C:\vcpkg\installed\x64-windows\include

  3. vscode 装了插件:C/C++ v1.20.5

  4. 在 google 操作之后下来,之后相关文件内容如下:

c_cpp_properties.json:

{ "configurations": [ { "name": "Win32", "includePath": [ "${workspaceFolder}/**", "C:/vcpkg/installed/x64-windows/include", "C:/MinGW/include" ], "defines": [], "windowsSdkVersion": "10.0.18362.0", "cStandard": "c11", "cppStandard": "c++17", "intelliSenseMode": "windows-gcc-x64" } ], "version": 4 } 

tasks.json:

{ "version": "2.0.0", "tasks": [ { "type": "shell", "label": "gcc build active file", "command": "gcc", "args": [ "-g", "${file}", "-I", "C:/vcpkg/installed/x64-windows/include", "-L", "C:/vcpkg/installed/x64-windows/lib", "-lfmt", "-o", "${fileDirname}/${fileBasenameNoExtension}.exe" ], "group": "build" }, { "type": "cppbuild", "label": "C/C++: gcc.exe build active file", "command": "C:\\MinGW\\bin\\gcc.exe", "args": [ "-fdiagnostics-color=always", "-g", "${file}", "-o", "${fileDirname}\\${fileBasenameNoExtension}.exe" ], "options": { "cwd": "${fileDirname}" }, "problemMatcher": [ "$gcc" ], "group": { "kind": "build", "isDefault": true }, "detail": "Task generated by Debugger." } ] } 

launch.json:

{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "(gdb) Attach", "type": "cppdbg", "request": "attach", "program": "${workspaceRoot}/${fileBasenameNoExtension}.exe", "MIMode": "gdb", "miDebuggerPath": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true }, { "description": "Set Disassembly Flavor to Intel", "text": "-gdb-set disassembly-flavor intel", "ignoreFailures": true } ] } ] } 

测试脚本

#include <fmt/format.h> int main(void){ int a = 1; fmt::print("a is {}", a); return 0; } 
]]>
将资源嵌入到可执行文件中并保持目录结构 tag:www.v2ex.com,2024-07-08:/t/1055626 2024-07-08T04:40:18Z 2024-07-08T09:02:19Z zhng920823 member/zhng920823 保持目录结构可以方便根据路径查找使用.

chmod +x embed_file_c.sh ./embed_file_c.sh dir2c_test # or ./embed_file_c.sh dir2c /path/to/resource > _data_enc.c gcc embed_file.c -o embed_file ./embed_file 
#include <stdio.h> #include <stdint.h> int main(int argc, char *argv[]) { #define _DATA_ENC_INC 1 #include "_data_enc.c" for (int i = 0; i < sizeof(_data_enc_lst)/sizeof(_data_enc_lst[0]); ++i) { printf("%3u, %3u, %s\n", _data_enc_lst[i].off, _data_enc_lst[i].len, _data_enc_lst[i].path); // &_data_enc[_data_enc_lst[i].off] is the data ptr, use it by need } return 0; } 

代码 https://github.com/webd90kb/webd/tree/master/codes/scripts/embed_file_c

]]>
一个简单实用的 C 工程示例, 附简洁的 Makefile tag:www.v2ex.com,2024-07-08:/t/1055621 2024-07-08T04:06:48Z 2024-07-08T13:32:50Z zhng920823 member/zhng920823 Makefile 可以做到如下简洁, 实际用时只要修改 EXE 和 OBJ 的列表值, 无需关心其它的东西

EXE=\ exe1\ a/exe2\ a/exe3\ OBJ=\ mod1\ liba/mod2\ liba/mod3\ include inc.mak 

工程源码 https://github.com/webd90kb/webd/tree/master/codes/c_project_template

]]>
关于 C 语言的相关问题 tag:www.v2ex.com,2024-06-02:/t/1046163 2024-06-02T13:20:27Z 2024-06-02T23:00:33Z Canglin member/Canglin 轮子更新: C/C++ 跨平台小工具库 tag:www.v2ex.com,2024-05-31:/t/1045670 2024-05-31T05:24:48Z 2024-05-31T05:59:25Z Neur2n member/Neur2n 忘记上链接了:https://github.com/neur1n/x.h

一万年前写了个C 语言跨平台小工具库,提到一开始是用 C++写的,后来改成了 C99 。结果现在又搞了一个 C++版本( C 版本还保留且两个版本同步)。代码更新了不少,也重新组织了,文档从 README 改成了 doxygen (但是不知道要怎么导入 github wiki )。主要功能有:

其实是一个很个人的工具库,平时工作用到什么就加什么进去。近期在玩 cuda ,所以也加了点 cuda 的东西。

]]>
为什么下列程序进行的是无符号乘法? tag:www.v2ex.com,2024-04-19:/t/1034018 2024-04-19T09:55:09Z 2024-04-19T13:03:08Z rookiemaster member/rookiemaster unsigned int x1 = 0xaeb1c2aa; unsigned int x2 = 0xaeb1c2aa; long long r_whole = (signed long long)x1 * (signed long long)x2; printf("r_whole = %lld\n",r_whole);

输出的结果是无符号乘法的结果:

r_whole = 8590088583138384100 

而下面的程序

unsigned int x1 = 0xaeb1c2aa; unsigned int x2 = 0xaeb1c2aa; int xx1 = x1; int xx2 = x2; long long r_whole = (signed long long)xx1 * (signed long long)xx2; printf("r_whole = %lld\n",r_whole); 

输出的事有符号乘法的结果:

r_whole = 1860719719092984036 

第一段程序里我不是对 x1 进行强制类型转换了吗

]]>
用 riscv64- Linux -gnu-gcc 编译的 c 文件为啥能在 x8664 下运行? tag:www.v2ex.com,2024-04-04:/t/1029725 2024-04-04T10:10:33Z 2024-04-04T12:04:07Z rookiemaster member/rookiemaster 就一个简单的 c 程序:

#include <stdio.h> int main() { printf("Hello, World\n"); } 

使用如下命令编译:

riscv64-linux-gnu-gcc --static hello.c -o hello_riscv64 

居然可以直接运行

> ./hello_riscv64 Hello, World 

使用 qemu 也能运行:

> qemu-riscv64-static hello_riscv64 Hello, World 

readelf 结果:

> readelf -h hello_riscv64 ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: RISC-V Version: 0x1 Entry point address: 0x1054c Start of program headers: 64 (bytes into file) Start of section headers: 501880 (bytes into file) Flags: 0x5, RVC, double-float ABI Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 7 Size of section headers: 64 (bytes) Number of section headers: 28 Section header string table index: 27 
]]>
char *s = "0123"和 char s[] = "0123"的区别 tag:www.v2ex.com,2024-03-29:/t/1028174 2024-03-29T08:39:15Z 2024-03-29T21:18:28Z rookiemaster member/rookiemaster 为什么前者不可以修改,后者可以修改,是因为存储区域的问题吗?

]]>
有人能完整地解释一下 int (*daytab) [13]和 int *daytab[13]吗 tag:www.v2ex.com,2024-03-25:/t/1026918 2024-03-25T11:43:55Z 2024-03-25T19:16:50Z rookiemaster member/rookiemaster 在 c 语言中, int a;是 declaration 还是 definition tag:www.v2ex.com,2024-03-22:/t/1026188 2024-03-22T12:01:29Z 2024-03-25T10:27:43Z rookiemaster member/rookiemaster 已知 extern int a;是 declaration

]]>
一个简单(奇怪)的 C 语言问题 tag:www.v2ex.com,2024-03-22:/t/1026058 2024-03-22T04:33:46Z 2024-03-23T01:40:42Z WilliamColton member/WilliamColton 使用的 IDE 是 CLion ,编译器是其自带的 MinGW 11.0 w64

下面的代码使用 chatgpt 加了注释,主要的问题是:

在直接给定输入

8 1 9 2 6 0 8 1 7 

时,结果为 0.

而再次给定输入

8 1 9 2 6 90 8 1 7 

时,结果还是 0.

而手动一个一个输入则正常显示 1.

虽然描述的很离奇,但是确实是这样,即:第一次的结果会影响第二次的结果,使之变成第一次的结果

我问了我们教 C 语言的老师,连他也不知道怎么回事,说可能是 CLion 的问题,但是这个答案并不能令我信服,

故来寻求各位 V 友帮助

求各位 V 友解答 QAQ

代码如下:

#include <stdio.h> int main() { int n; scanf("%d", &n); // 从标准输入中读取一个整数,存储到变量 n 中 int min; scanf("%d", &min); // 假设输入的第一个数为最小值,存储到变量 min 中 int num; for (int i = 1; i < n; i++) { // 循环读取剩余的 n-1 个整数 scanf("%d", &num); // 从标准输入中读取一个整数,存储到变量 num 中 printf("%d\n", num); // 将读取的整数打印到标准输出,以换行符结束 if (num < min) { // 检查当前读取的整数是否比最小值小 min = num; // 更新最小值为当前读取的整数 } } printf("%d", min); // 打印最小值到标准输出 return 0; // 返回 0 表示程序正常结束 } 
]]>
c 语言中打印指针的值打印的是 OS 分配的虚拟地址的值吗?要怎么知道 OS 给这个 c 程序进程分配的虚拟地址的大小呢? tag:www.v2ex.com,2024-03-20:/t/1025538 2024-03-20T12:33:52Z 2024-03-21T02:55:32Z rookiemaster member/rookiemaster 并且如何知道哪一块内存大小是可以读可以写,以避免出现下面程序的 Segmentation Fault 呢?

#include<stdio.h> int main(){ int *p = (int *)0x1; printf("%p\n", p); *p = 1; // segmentation fault printf("%d", *p); // segmentation fault } 
]]>
gcc 是怎么找到 system 函数的定义(实现)的? tag:www.v2ex.com,2024-03-20:/t/1025303 2024-03-20T02:23:35Z 2024-04-04T18:57:56Z rookiemaster member/rookiemaster 程序如下:

int main() { system("echo hello world"); } 

使用 gcc 编译时会警告找不到 system 的声明:

DoubleHash.c: In function ‘main’: DoubleHash.c:3:5: warning: implicit declaration of function ‘system’ [-Wimplicit-function-declaration] 3 | system("echo hello world"); | ^~~~~~ 

但是程序仍然可以运行,我想知道编译器是怎么找到 system 的定义的?

]]>
用 C 实现轻量级表达式完成策略定制化和模板内容生成 tag:www.v2ex.com,2024-03-19:/t/1025025 2024-03-19T04:39:01Z 2024-03-19T00:38:01Z monkeyNik member/monkeyNik 本文介绍开源 C 语言库Melon表达式组件,该组件实现了一个轻量级表达式,允许开发者定制化属于自己的专属变量和函数解析器。该模块可以用于文本模板内容替换以及一些简单的指令模式编程场景(例如编写策略)。

下面是该组件支持的语法:

abc --这是一个变量 "abc" --这是一个字符串常量 'abc' --这也是字符串常量 1 --整数 1.2 --浮点数 0xa --十六进制整数 0311 --八进制整数 concat(abc, bcd) --这是一个函数,参数有两个,都是变量 concat(abc, "bcd") --这是一个函数,参数有两个,一个是变量,一个是常量 concat(1, "bcd") --两个参数都是常量 concat("abc", concat(bcd, "efg")) --这个例子展示了函数嵌套调用 concat("abc", concat(bcd, "efg")) aaa concat("bcd", concat(efg, "hij")) --这个例子展示运行多个表达式 

简单来说,表达式语法支持三种类型内容:

并且函数支持嵌套调用。

此外,可以一次执行多个表达式,所有表达式的执行结果为最后一个表达式的结果。

我们分别对着三种类型分别说明:

常量

这个比较好理解,就是字面量,主要支持:字符串、整数和浮点数。其中,整数支持十进制写法、八进制写法和十六进制写法。

变量

变量顾名思义,就是其值可变。但由于表达式比较简单,且应用场景与常规编程语言不同,因此不是通过=来进行赋值的,而是通过回调函数,由使用者决定该返回何值作为该变量的值。

函数

与变量一样,函数的行为完全由回调函数决定,也就是说由使用者自行定制。

例子

我们看一个示例

#include "mln_expr.h" #include "mln_log.h" #include <stdio.h> static mln_expr_val_t *func_expr_handler(mln_string_t *name, int is_func, mln_array_t *args, void *data) { mln_expr_val_t *v, *p; int i; mln_string_t *s1 = NULL, *s2, *s3; if (!is_func) return mln_expr_val_new(mln_expr_type_string, name, NULL); for (i = 0, v = p = mln_array_elts(args); i < mln_array_nelts(args); v = p + (++i)) { if (s1 == NULL) { s1 = mln_string_ref(v->data.s); continue; } s2 = v->data.s; s3 = mln_string_strcat(s1, s2); mln_string_free(s1); s1 = s3; } v = mln_expr_val_new(mln_expr_type_string, s1, NULL); mln_string_free(s1); return v; } int main(void) { mln_string_t func_exp = mln_string("concat('abc', concat(aaa, 'bbb')) ccc concat('eee', concat(bbb, 'fff'))"); mln_expr_val_t *v; v = mln_expr_run(&func_exp, func_expr_handler, NULL); if (v == NULL) { mln_log(error, "run failed\n"); return -1; } mln_log(debug, "%d %S\n", v->type, v->data.s); mln_expr_val_free(v); return 0; } 

这是一个综合一点的例子,这个例子中包含了函数嵌套调用、变量、多表达式执行。

表达式中的变量和函数都由func_expr_handler这个回调函数来解析。对于变量,回调函数直接返回变量的名字作为变量的值。而对于函数,回调函数则是将函数参数拼接成一个字符串作为函数的返回值。

由于本例中存在三个表达式:

前面我们说到过,mln_expr_run的返回值是最后一个表达式的值,所以最终终端的输出就是:

eeebbbfff 

也正如这个例子所示,表达式组件只是提供了一种对文本格式的规范,而具体有哪些函数和变量都完全交给回调函数来决定,也就是交给了使用者决定。并且这个组件并不像完整的编程语言那样功能繁重,因此比较适合一些小型功能整合或者模板替换之类的场景。

模板替换可以参考 web 前端的那些模板,例如:twig 、mustache 等。

小功能整合举个例子,例如在对某种网络通信中,我们需要对报文提取某些字段,然后对字段处理,然后再做验证。那么提取、处理、验证就可以被封装成三个函数。这三个函数是三种行为,而不是策略。我们可以将这三种行为应设成表达式组件中的三个函数,然后我们就可以通过对这三个函数的组合应用来实现策略。对于策略的改变,我们并不需要修改 C 代码,只需要将策略的文本内容(也就是这些表达式)做一些修改即可。

感谢阅读!

]]>
c 语言是如何给汉字编码的? tag:www.v2ex.com,2024-03-18:/t/1024844 2024-03-18T11:19:36Z 2024-03-18T17:56:45Z rookiemaster member/rookiemaster 如下程序:

#include <stdio.h> int main() { char s[7] = "七 123"; int i; for (i = 0; i < 7; i++) { printf("%d,", s[i]); } } 

打印出来的结果是:-28,-72,-125,49,50,51,0,
前面的-28,-72,-125 正是七的 utf8 编码
要是我想打印出七的 gbk 编码应该怎么办?

]]>
还是不太理解 C 静态库和动态库? tag:www.v2ex.com,2024-03-09:/t/1022210 2024-03-09T22:20:25Z 2024-03-14T19:12:08Z nnegier member/nnegier 我看到一个描述,“为了方便,我们需要把 moon.o 、sun.o 、earth.o 这 3 个东西弄成一个东西。这种方法 就是 静态库 跟 动态库,静态库 是可以链接进去 程序自身,动态库是共享库,可以由多个程序共享节省空间,动态库只有用到的时候才会加载。”

它这里的程序是指单个应用还是指单个 C 语言文件呀?如果是单个应用,怎么说多个程序共享呢;如果是说单个 C 文件,那所谓的共享又是指啥呢;反正我横竖都弄不清,只知道都能用。(我个人背景是上层 Android 开发,使用的时候感觉没啥太大区别,当然了静态库快、动态库慢这个我能理解,像空间节省这个我没能理解)

]]>
开源 C 库 bfdev: MPI 大数运算 tag:www.v2ex.com,2024-03-07:/t/1021646 2024-03-07T17:33:56Z 2024-03-06T17:33:56Z Openbfdev member/Openbfdev 本文展示开源 C 库 bfdev 的大数运算模块。我们将分别在 x86 Linux 平台和 py32f030 为代表的嵌入式平台上分别进行演示,并进行性能测试。

关于 bfdev 库,这是一个开源的 C 语言算法库, 它具有:良好的可移植性,面向对象的方法设计、安装部署简单等等优势。

Github 仓库链接

简介

MPI 即 Multi precision integer (多精度整数),就是对很大的数进行一系列的运算。在数学中,数的大小是没有上限的,但是在计算机中,由于受 ALU 字长的限制,处理器无法对其进行直接计算,我们就需要用到多精度整数算法。

使用

我们先给出两个平台基于 MPI 使用 machin 公式 计算圆周率的测试代码,再进行说明。

bfdev 仓库 示例

#define MODULE_NAME "mpi-machin" #define bfdev_log_fmt(fmt) MODULE_NAME ": " fmt #include <stdio.h> #include <bfdev/mpi.h> #include <bfdev/log.h> #include "../time.h" #include "helper.h" #define TEST_LEN 10000 #define TEST_SIZE (TEST_LEN / 4 + 1) #define TEST_LOOP (TEST_LEN / 1.39793 + 1) #define PRINT_RESULT 1 int main(int argc, const char *argv[]) { bfdev_mpi_t *vw, *vs, *vv, *vq; unsigned int k; int retval; if (!((vw = bfdev_mpi_create(NULL)) && (vs = bfdev_mpi_create(NULL)) && (vv = bfdev_mpi_create(NULL)) && (vq = bfdev_mpi_create(NULL)))) return 1; /** * Machin-like formula: * PI = 16arctan(1/5) - 4arctan(1/239) * * These formulas are used in conjunction with Gregory's * series, the Taylor series expansion for arctangent: * arctan(x) = x - (x^3)/3 + (x^5)/5 - (x^7)/7 + ... */ if ((retval = bfdev_mpi_seti(vw, 16 * 5)) || (retval = bfdev_mpi_seti(vv, 4 * 239)) || (retval = bfdev_mpi_seti(vq, 10000))) return retval; for (k = 0; k < TEST_SIZE; ++k) { if ((retval = bfdev_mpi_mul(vw, vw, vq)) || (retval = bfdev_mpi_mul(vv, vv, vq))) return retval; } bfdev_log_info("Convergence Machin %d:\n", TEST_LEN); EXAMPLE_TIME_STATISTICAL( for (k = 1; k <= TEST_LOOP; ++k) { if ((retval = bfdev_mpi_divi(vw, vw, vw, 5 * 5)) || (retval = bfdev_mpi_divi(vv, vv, vv, 239 * 239)) || (retval = bfdev_mpi_sub(vq, vw, vv)) || (retval = bfdev_mpi_divi(vq, vq, vq, 2 * k - 1))) return retval; if (k & 1) retval = bfdev_mpi_add(vs, vs, vq); else retval = bfdev_mpi_sub(vs, vs, vq); if (retval) return retval; } 0; ); #if PRINT_RESULT char *result; result = print_num(vs, 10); if (!result) return 1; printf("%c.", *result); puts(result + 1); free(result); #endif bfdev_mpi_destory(vw); bfdev_mpi_destory(vs); bfdev_mpi_destory(vv); bfdev_mpi_destory(vq); return 0; } 

以下是运算 pi 后 10000 位的结果,共使用了 0.36 秒。

❯ ./build/examples/mpi/mpi-machin [info] mpi-machin: Convergence Machin 10000: [info] mpi-machin: real time: 0.360000 [info] mpi-machin: user time: 0.350000 [info] mpi-machin: kern time: 0.000000 3.14159 ... 

py32f030 平台 示例

#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <errno.h> #include <bfdev.h> #include "main.h" #include "py32f0xx_hal.h" #define TEST_LEN 100 #define TEST_SIZE (TEST_LEN / 4 + 1) #define TEST_LOOP (TEST_LEN / 1.39793 + 1) int mpi_benchmark(void) { uint32_t start, time; bfdev_mpi_t *vw, *vs, *vv, *vq; unsigned int k; int retval; if (!((vw = bfdev_mpi_create(NULL)) && (vs = bfdev_mpi_create(NULL)) && (vv = bfdev_mpi_create(NULL)) && (vq = bfdev_mpi_create(NULL)))) return 1; if ((retval = bfdev_mpi_seti(vw, 16 * 5)) || (retval = bfdev_mpi_seti(vv, 4 * 239)) || (retval = bfdev_mpi_seti(vq, 10000))) return retval; for (k = 0; k < TEST_SIZE; ++k) { if ((retval = bfdev_mpi_mul(vw, vw, vq)) || (retval = bfdev_mpi_mul(vv, vv, vq))) return retval; } bfdev_log_info("Convergence Machin %d:\n", TEST_LEN); start = HAL_GetTick(); for (k = 1; k <= TEST_LOOP; ++k) { if ((retval = bfdev_mpi_divi(vw, vw, vw, 5 * 5)) || (retval = bfdev_mpi_divi(vv, vv, vv, 239 * 239)) || (retval = bfdev_mpi_sub(vq, vw, vv)) || (retval = bfdev_mpi_divi(vq, vq, vq, 2 * k - 1))) return retval; if (k & 1) retval = bfdev_mpi_add(vs, vs, vq); else retval = bfdev_mpi_sub(vs, vs, vq); if (retval) return retval; iwdg_touch(); } bfdev_mpi_destory(vw); bfdev_mpi_destory(vs); bfdev_mpi_destory(vv); bfdev_mpi_destory(vq); iwdg_touch(); time = HAL_GetTick() - start; bfdev_log_notice("Total time: %lu.%lus\n", time / 1000, time % 1000); return 0; } 

以下是运算结果,由于 py32f030 只有 4KiB RAM ,我们这里只计算 100 位。

[info] benchmark: Benchmark for PY32F0xx. [info] benchmark: Bfdev version: 1.0.1-devel [info] benchmark: This may take a few minutes... [info] Convergence Machin 100: [notice] Total time: 0.21s ... 

我们计算的大体流程如下:

  1. 首先使用 bfdev_mpi_create 创建 MPI 对象
  2. MPI 对象使用 bfdev_mpi_seti 方法赋值
  3. 使用计算方法进行运算
  4. 输出完成并退出

bfdev 中,使用 MPI 需要引入 <bfdev/mpi.h> 头文件,或者引入 <bfdev.h> 顶层头文件也可以。

这里要重点介绍一下 bfdev_mpi_create(alloc) 函数,它使用了 bfdev 的 allocator 兼容层,传入 NULL 将使用系统默认内存分配器,用户也可以使用自己的内存池为 MPI 分配内存。

结语

欢迎各位感兴趣的读者去访问 bfdev 仓库。

bfdev 文档

]]>
C 语言中的面向切面编程(AOP) tag:www.v2ex.com,2024-03-07:/t/1021620 2024-03-07T14:11:06Z 2024-03-25T13:51:14Z monkeyNik member/monkeyNik 概念

首先给出一段由 ChatGPT 给出的简短的 AOP 概念:

AOP 是一种编程方法,用来将在程序中多处重复出现的代码(比如日志、权限控制)从主要业务逻辑中抽取出来,提高代码的模块化和可维护性。

抽取后的代码会在原始的业务逻辑代码中特定的位置执行,这些位置由切点( Pointcut )定义。通常会在方法执行前、执行后、抛出异常时等特定点执行抽取出的代码,这些点被称为连接点( Join Point )。

概述

在 C 语言中,编译器所提供的编译期和执行期的能力相较于 java 或者其他语言来说会弱一些,这也许就是可能很少听到在 C 语言中搞面向切面编程的原因之一吧。

从上面的概念上来看,AOP 一般是在一些函数(或类方法)执行前后做一些额外处理,例如调用前增加一些权限控制,调用后增加一些日志记录。从这些行为上来说,任何语言其实都可以做到。我们可以简单的在一个函数的开始加一段逻辑或调用某个函数来实现权限验证,在函数返回前调用某个函数添加日志等等。类似如下代码:

void foo(void) { if (!verify_identity()) return; //... log("end"); } 

但很显然,这么做会在程序的很多个函数中添加很多重复的代码(例如本例的verify_identitylog),以至于代码变得比较臃肿。

那么有没有什么办法来瘦身呢?

这就是 AOP 擅长的领域了。

写在示例之前

C 语言编译器没有提供很完整的 AOP 支持,因此我们需要自行手动实现,或者使用一些现有的库来实现。

本文将使用开源 C 语言库 Melon 的函数模板来实现上面的效果。

在 Melon 提供的函数模板组件中,实现了若干宏函数,这些宏函数都是用来定义不同类型的函数的。这些用宏来定义的函数和我们原生 C 语言中的函数的区别,简单来说就是,在我们实际要执行的函数逻辑外,再封装一个函数,这个函数会在我们指定的函数逻辑开始前和结束后调用一个回调函数(即函数的入口回调函数出口回调函数)。

基于函数模板的这一特性,Melon 中实现了一个 span 组件,用来度量使用函数模板定义的函数的时间开销。

但如果事情仅限于此,那么这种 AOP 很显然能做到的事情也基本仅限于此了。

因此,Melon 支持了 c99 ,并利用 c99 提供的宏特性,实现了将函数模板定义的函数的实参以可变参数的形式传递到入口和出口回调函数中。这就意味着,入口和出口回调函数可以访问函数的参数,并对参数的内容作出修改(主要针对指针指向的内存中的数据)。

这样,就给我们在回调函数中提供了更多的可操作空间。我们可以针对不同的函数,修改其参数值,从而来影响后续函数调用中的执行逻辑。例如前面的权限验证,我们可以将其大致简化为如下形式:

void entry_callback(char *file, char *func, int line, ...) { va_list args; va_start(args, line); int *a = (int *)va_arg(args, int *); va_end(args); if (!verify_identity()) *a = 0; } void exit_callback(char *file, char *func, int line, ...) { va_list args; va_start(args, line); int *a = (int *)va_arg(args, int *); va_end(args); log("%d\n", *a); } void foo(int *a) { if (!*a) return; //... } int bar(int *d, int e) { if (!*d) return -1; //... return 0; } 

这里的代码只是一个示意,后面会给出一个实际可用的示例。

我们可以随意增加函数,这些函数都会利用同一对入口和出口函数来实现身份验证。

示例

下面就给出一个可用的使用函数模板实现 AOP 的 C 语言代码。

//a.c #include "mln_func.h" #include <stdio.h> #include <string.h> #include <stdarg.h> MLN_FUNC_VOID(static, void, foo, (int *a, int b), (a, b), { printf("in %s: %d\n", __FUNCTION__, *a); *a += b; }) MLN_FUNC(static, int, bar, (void), (), { printf("%s\n", __FUNCTION__); return 0; }) static void my_entry(const char *file, const char *func, int line, ...) { if (strcmp(func, "foo")) return; va_list args; va_start(args, line); int *a = (int *)va_arg(args, int *); va_end(args); printf("entry %s %s %d %d\n", file, func, line, *a); ++(*a); } static void my_exit(const char *file, const char *func, int line, ...) { if (strcmp(func, "foo")) return; va_list args; va_start(args, line); int *a = (int *)va_arg(args, int *); va_end(args); printf("exit %s %s %d %d\n", file, func, line, *a); } int main(void) { int a = 1; mln_func_entry_callback_set(my_entry); mln_func_exit_callback_set(my_exit); foo(&a, 2); return bar(); } 

这段函数中,我们使用MLN_FUNCMLN_FUNC_VOID来定义了两个函数,即foobar。两个函数的逻辑很简单,就是 printf 输出当前函数名以及参数值(如果有参数的话)。同时,我们也使用了mln_func_entry_callback_setmln_func_exit_callback_set定义了两个全局回调函数,用来在函数调用开始和结束时调用。

我们可以看到,回调函数中使用strcmp对进入回调的函数做了过滤,仅对foo函数做额外处理。在入口回调中输出函数信息及第一个参数的值,随后修改参数指针指向的内存中的值。在出口回调中输出函数信息和参数值。

我们来编译一下(我们假定这个代码文件名为a.c):

cc -o a a.c -I /usr/local/melon/include/ -L /usr/local/melon/lib/ -lmelon -std=c99 -DMLN_C99 -DMLN_FUNC_FLAG 

这里:

执行一下看看效果:

entry a.c foo 6 1 in __mln_func_foo: 2 exit a.c foo 6 4 __mln_func_bar 

可以看到:

最后,我们去掉MLN_FUNC_FLAG这个宏再次编译一次:

cc -o a a.c -I /usr/local/melon/include/ -L /usr/local/melon/lib/ -lmelon -std=c99 -DMLN_C99 

然后执行一下看看输出结果:

in foo: 1 bar 

可以看得出,此时foobar不再是封装函数,而是我们定义的函数逻辑的函数名,即普通的 C 语言函数。

读到这里的都是真爱,感谢阅读!

]]>
网络语言污染/扭曲专业术语名词——《为什么 c 语言这么抽象?》 tag:www.v2ex.com,2024-02-21:/t/1017363 2024-02-21T14:04:14Z 2024-02-22T07:00:12Z cnbatch member/cnbatch 知乎上有人发了个提问,《为什么 c 语言这么抽象?》
提问者觉得位运算“抽象”:
https://www.zhihu.com/question/627863026

一开始我实在是莫名其妙,位运算这么简单具体的操作,哪来的“抽象”?

然后再看评论区、回答区,才明白原来提问人的“抽象”竟然是网络语言的语境,而不是专业术语的语境。

这次“抽象”二字的用法可真够抽象的。
(严谨的备注:最后一个“抽象”是网络用语)

]]>
C 语言函数资源开销可观测性 tag:www.v2ex.com,2024-01-27:/t/1012060 2024-01-27T08:52:08Z 2024-01-27T04:51:08Z monkeyNik member/monkeyNik 之前的文章提及过开源 C 语言库Melon函数模板。使用函数模板来扩展函数功能。

今天,我们介绍 Melon 中的span组件,使用它来轻松监控函数的调用耗时情况。

概述

Melon 中的资源开销( span )组件是用来测量 C 语言函数开销的,这个模块需要配合函数模板模块一同使用,因此也需要定义MLN_FUNC_FLAG才会使得函数模板功能启用,进而实现函数开销的跟踪。

目前支持的开销如下:

头文件

#include "mln_span.h" 

模块名

span

在 Melon 中支持模块选择性编译,因此可以选择指定的模块进行编译,脚本将自行计算该模块所依赖的 Melon 中的其他模块并一同进行编译。

示例

下面我们一起看一个多线程的示例,用来展示mln_span接口的使用以及在多线程环境下的效果。

//a.c #include <pthread.h> #include "mln_span.h" #include "mln_func.h" MLN_FUNC(int, abc, (int a, int b), (a, b), { return a + b; }) MLN_FUNC(static int, bcd, (int a, int b), (a, b), { return abc(a, b) + abc(a, b); }) MLN_FUNC(static int, cde, (int a, int b), (a, b), { return bcd(a, b) + bcd(a, b); }) void *pentry(void *args) { int i; mln_span_start(); for (i = 0; i < 10; ++i) { cde(i, i + 1); } mln_span_stop(); mln_span_dump(); mln_span_release(); return NULL; } int main(void) { int i; pthread_t pth; pthread_create(&pth, NULL, pentry, NULL); for (i = 0; i < 10; ++i) { bcd(i, i + 1); } pthread_join(pth, NULL); return 0; } 

前面我们说过,span 组件需要配合函数模板组件一同使用。这里我们使用函数模板组件定义了三个函数abc, bcd, cde

然后我们在main函数中启动一个线程,并在线程入口函数内,调用mln_span_start开启资源消耗跟踪。然后调用cde函数十次。而在main函数中,在创建线程后,循环调用bcd函数十次,最后等待线程退出,程序结束。

我们对这段程序进行编译:

cc -o a a.c -I /usr/local/melon/include/ -L /usr/local/melon/lib/ -lmelon -DMLN_FUNC_FLAG -lpthread 

注意,这里-DMLN_FUNC_FLAG是用来启用函数模板模块的功能。如果不定义这个宏,那么使用MLN_FUNC定义的函数就是普通 C 语言函数,不会启用任何跟踪能力。

编译好后运行程序,可以看到类似如下的输出:

| pentry at a.c:20 takes 92 (us) | cde at a.c:13 takes 4 (us) | bcd at a.c:9 takes 1 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | bcd at a.c:9 takes 1 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | cde at a.c:13 takes 5 (us) | bcd at a.c:9 takes 0 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | bcd at a.c:9 takes 2 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | cde at a.c:13 takes 24 (us) | bcd at a.c:9 takes 1 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | bcd at a.c:9 takes 21 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | cde at a.c:13 takes 5 (us) | bcd at a.c:9 takes 1 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | bcd at a.c:9 takes 1 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | cde at a.c:13 takes 3 (us) | bcd at a.c:9 takes 2 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | bcd at a.c:9 takes 1 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | cde at a.c:13 takes 30 (us) | bcd at a.c:9 takes 24 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 1 (us) | bcd at a.c:9 takes 6 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | cde at a.c:13 takes 3 (us) | bcd at a.c:9 takes 2 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | bcd at a.c:9 takes 1 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | cde at a.c:13 takes 3 (us) | bcd at a.c:9 takes 2 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 1 (us) | bcd at a.c:9 takes 1 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | cde at a.c:13 takes 7 (us) | bcd at a.c:9 takes 1 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) | bcd at a.c:9 takes 2 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 1 (us) | cde at a.c:13 takes 3 (us) | bcd at a.c:9 takes 2 (us) | abc at a.c:5 takes 1 (us) | abc at a.c:5 takes 0 (us) | bcd at a.c:9 takes 0 (us) | abc at a.c:5 takes 0 (us) | abc at a.c:5 takes 0 (us) 

最后

感兴趣的小伙伴欢迎访问Melon 的 Github进行试用。Melon 是一个跨平台的 C 语言库,内含多种数据结构、算法和常用组件。Melon 并不是一个纯粹的数据结构和算法库,而是致力于提供开发中常用的组件,如:内存池、线程池、多进程框架、可观测性等。Melon 提供中英文文档,内含各模块接口说明和示例代码,便于开发者快速上手。

感谢阅读!

]]>
C 语言用户态函数可观测性 tag:www.v2ex.com,2024-01-23:/t/1010847 2024-01-23T04:08:57Z 2024-01-23T02:07:57Z monkeyNik member/monkeyNik 本文不是介绍 eBPF 相关的用户态 Probe 的内容,而是如何利用开源 C 语言库Melon的函数模板来轻松实现函数的可观测性需求,例如:测量耗时等。

本文主要介绍的是 Melon 库中的func模块,之所以没有给这个模块起名叫可观测性或者span,原因是这是一个更为通用的模块,不仅限于可观测性的需求。

func模块实现的功能与 GCC 的 constructor 和 destructor 特性十分相似,就是在 C 语言函数的入口和出口增加用户自定义回调函数,在调用函数时自行调用这些函数。

我们先看一个简单的例子:

// a.c #include "mln_func.h" MLN_FUNC(int, abc, (int a, int b), (a, b), { printf("in %s\n", __FUNCTION__); return a + b; }) MLN_FUNC(static int, bcd, (int a, int b), (a, b), { printf("in %s\n", __FUNCTION__); return abc(a, b) + abc(a, b); }) static void my_entry(const char *file, const char *func, int line) { printf("entry %s %s %d\n", file, func, line); } static void my_exit(const char *file, const char *func, int line) { printf("exit %s %s %d\n", file, func, line); } int main(void) { mln_func_entry_callback_set(my_entry); mln_func_exit_callback_set(my_exit); printf("%d\n", bcd(1, 2)); return 0; } 

这段代码中,使用MLN_FUNC定义了两个函数,分别为abcbcd,且在bcd中会调用abc。其实这个模板宏相对比较容易理解,其宏函数参数顺序如下:

这里唯一有些困惑的是实参列表,这与宏的实现有关。我们以abc为例,简述一下实现原理。

原理:这个宏会定义两个函数,一个名为abc,一个名为__abc。函数体其实对应的是__abc,也就是说__abc才是真正我们期望调用的那个函数,而abc是对__abc的一个封装,会在__abc的调用前后调用自定义回调函数。

而实参列表就是在函数abc中调用__abc时需要给__abc传递的参数,所以这个参数列表其实就是形参列表去掉类型之后的名字和顺序。

这个实参列表无法忽略,是因为__abc不能省略,而__abc不能省略是因为函数体中可能包含 return 语句,因此我们无法完全隐式地在 return 前,甚至是在 return 的表达式计算后真正的返回前调用回调函数。所以必须单独定义成一个函数也就是__abc

下面我们来编译这个程序:

cc -o a a.c -I /path/to/melon/include -L /path/to/melon/lib -lmelon 

其中/path/to/melon的部分是 Melon 的安装路径,默认一般是/usr/local/melon

然后运行一下

./a in bcd in abc in abc 6 

你会发现回调函数完全没被调用。这不是我们的代码有问题,而是我们并未启用模板功能。模板启用需要编译时存在MLN_FUNC_FLAG的宏定义,我们既可以将它定义在源文件中,也可以在编译时作为命令行参数给出。下面我以后者为例展示:

cc -o a a.c -I /path/to/melon/include -L /path/to/melon/lib -lmelon -DMLN_FUNC_FLAG 

再次运行

./a entry a.c bcd 10 in __bcd entry a.c abc 5 in __abc exit a.c abc 5 entry a.c abc 5 in __abc exit a.c abc 5 exit a.c bcd 10 6 

可以看到,回调函数都被正常调用了。

利用这个开关宏,我们可以在不修改任何代码的情况下,轻松切换是否需要开启这项功能。

综合示例

前面给出的例子比较简单,那么下面就来看一个实现测量函数调用耗时的例子吧。

这里我将给出三个文件:

其中,span.hspan.c可以随意复制粘贴使用,这是一个独立的模块,当然,你还需要先安装好 Melon 库。

span.h

#include <sys/time.h> #include "mln_array.h" typedef struct mln_span_s { struct timeval begin; struct timeval end; const char *file; const char *func; int line; mln_array_t subspans; struct mln_span_s *parent; } mln_span_t; extern int mln_span_start(void); extern void mln_span_stop(void); extern void mln_span_dump(void); extern void mln_span_release(void); 

这里定义了一个数据结构mln_span_t,用来存放函数调用的起始和结束时的时间戳,以及函数所在源文件的信息。还包含了这个函数中调用的其他函数的调用时长信息,以及一个指向上一级调用(也就是调用当前函数的函数)信息的指针。

也就是说,当我们的函数执行完毕后,我们遍历这个结构就能拿到完整的调用关系及其调用细节。

span.c

#include <stdlib.h> #include <string.h> #include "span.h" #include "mln_stack.h" #include "mln_func.h" static mln_stack_t *callstack = NULL; static mln_span_t *root = NULL; static void mln_span_entry(const char *file, const char *func, int line); static void mln_span_exit(const char *file, const char *func, int line); static mln_span_t *mln_span_new(mln_span_t *parent, const char *file, const char *func, int line); static void mln_span_free(mln_span_t *s); static mln_span_t *mln_span_new(mln_span_t *parent, const char *file, const char *func, int line) { mln_span_t *s; struct mln_array_attr attr; if (parent != NULL) { s = (mln_span_t *)mln_array_push(&parent->subspans); } else { s = (mln_span_t *)malloc(sizeof(mln_span_t)); } if (s == NULL) return NULL; memset(&s->begin, 0, sizeof(struct timeval)); memset(&s->end, 0, sizeof(struct timeval)); s->file = file; s->func = func; s->line = line; attr.pool = NULL; attr.pool_alloc = NULL; attr.pool_free = NULL; attr.free = (array_free)mln_span_free; attr.size = sizeof(mln_span_t); attr.nalloc = 7; if (mln_array_init(&s->subspans, &attr) < 0) { if (parent == NULL) free(s); return NULL; } s->parent = parent; return s; } static void mln_span_free(mln_span_t *s) { if (s == NULL) return; mln_array_destroy(&s->subspans); if (s->parent == NULL) free(s); } int mln_span_start(void) { struct mln_stack_attr sattr; mln_func_entry_callback_set(mln_span_entry); mln_func_exit_callback_set(mln_span_exit); sattr.free_handler = NULL; sattr.copy_handler = NULL; if ((callstack = mln_stack_init(&sattr)) == NULL) return -1; return 0; } void mln_span_stop(void) { mln_func_entry_callback_set(NULL); mln_func_exit_callback_set(NULL); mln_stack_destroy(callstack); } void mln_span_release(void) { mln_span_free(root); } static void mln_span_format_dump(mln_span_t *span, int blanks) { int i; mln_span_t *sub; for (i = 0; i < blanks; ++i) printf(" "); printf("| %s at %s:%d takes %lu (us)\n", \ span->func, span->file, span->line, \ (span->end.tv_sec * 1000000 + span->end.tv_usec) - (span->begin.tv_sec * 1000000 + span->begin.tv_usec)); for (i = 0; i < mln_array_nelts(&(span->subspans)); ++i) { sub = ((mln_span_t *)mln_array_elts(&(span->subspans))) + i; mln_span_format_dump(sub, blanks + 2); } } void mln_span_dump(void) { if (root != NULL) mln_span_format_dump(root, 0); } static void mln_span_entry(const char *file, const char *func, int line) { mln_span_t *span; if ((span = mln_span_new(mln_stack_top(callstack), file, func, line)) == NULL) { fprintf(stderr, "new span failed\n"); exit(1); } if (mln_stack_push(callstack, span) < 0) { fprintf(stderr, "push span failed\n"); exit(1); } if (root == NULL) root = span; gettimeofday(&span->begin, NULL); } static void mln_span_exit(const char *file, const char *func, int line) { mln_span_t *span = mln_stack_pop(callstack); if (span == NULL) { fprintf(stderr, "call stack crashed\n"); exit(1); } gettimeofday(&span->end, NULL); } 

这里就是耗时统计所需要的所有函数定义。利用一个栈数据结构来保证函数的调用关系,然后在函数的入口回调处创建mln_span_t结点记录起始时间和函数信息并入栈,在出口回调处记录结束时间并出栈。

a.c

#include "span.h" #include "mln_func.h" MLN_FUNC(int, abc, (int a, int b), (a, b), { return a + b; }) MLN_FUNC(static int, bcd, (int a, int b), (a, b), { return abc(a, b) + abc(a, b); }) int main(void) { mln_span_start(); bcd(1, 2); mln_span_stop(); mln_span_dump(); mln_span_release(); return 0; } 

这里还是那个配方,就是调用bcd,然后bcd调用abc。我们这次在main函数中使用span.h中声明的函数。

一起来简单编译一下:

cc -o a span.c a.c -I /usr/local/melon/include -L /usr/local/melon/lib -lmelon -DMLN_FUNC_FLAG 

然后运行一下:

./a | bcd at a.c:8 takes 2 (us) | abc at a.c:4 takes 0 (us) | abc at a.c:4 takes 0 (us) 

小结

Melon的函数模板其实设计之初也是为了可观测性,因为 GCC 仅支持了 constructor 和 destructor 。如果显式地在代码中加入各种跟踪函数调用,就会让整个函数定义看着非常不连贯和杂乱。因此选择了当前的这个使用方式,但也不可避免的引入了看似没什么用途的实参部分。

另外,Melon 库支持模块选择性编译,因此函数模版模块可以单独编译成库,换言之,这个模块是完全无操作系统依赖的,单片机的小伙伴们可以随意取用。

感谢阅读!

]]>
在 C 中,如何正确拷贝字符串 tag:www.v2ex.com,2023-12-03:/t/997278 2023-12-03T10:54:50Z 2023-12-03T14:44:21Z lcj2class member/lcj2class https://liujiacai.net/blog/2023/12/02/strcpy/

这两天稍微总结了下 strcpy 的问题,欢迎大家批评指正。 ]]>
c 语言中自定义 section 段问题请教 tag:www.v2ex.com,2023-11-22:/t/994162 2023-11-22T06:05:08Z 2023-11-22T16:34:39Z kkkbbb member/kkkbbb 看到一个代码自定义段实现如下:

typedef void (*myown_call)(void); #define func_init(func) \ __attribute__((section("myown"), aligned(__alignof__(myown_call)))) \ myown_call _fn_##func = func extern myown_call __start_myown; extern myown_call __stop_myown; void do_initcalls(void) { myown_call *pfun = &__start_myown; do { (*pfun)(); ++pfun; } while (pfun < &__stop_myown); } 

上面的代码是实现初始化逻辑的,我在网上看到要想实现自定义的段数据加载,需要在链接脚本中指定__start_myown 和__stop_myown 的地址位置,但是在代码的编译过程中没有看到相关的设置,不太清楚__start_myown 、__stop_myown 的地址编译器是如何分配的,又懂得大佬可以给解释下么?

]]>
C 语言的源码过滤功能的工具 tag:www.v2ex.com,2023-11-10:/t/990768 2023-11-10T10:15:20Z 2023-11-11T03:11:03Z Exxfire member/Exxfire 我是一名主要使用 C 语言开发的程序员。 工作中经常需要阅读代码,C 语言的不少工程的代码当中用到了很多的预处理指令+宏来控制打开、关闭软件的一些功能,这样使用起来很方便,但是比较影响阅读体验,有没有什么 vs code 的插件或者工具,可以把没打开部分的代码给过滤掉,类似预编译之后的代码,但是不会像预编译一样把头文件和宏展开。

]]>
求助 libcurl 的 curl_easy_pause() 使用方法 tag:www.v2ex.com,2023-11-08:/t/989854 2023-11-08T05:58:36Z 2023-11-08T05:57:36Z kanhongj member/kanhongj 官方 API 说明

意思是最好在

CURLOPT_WRITEFUNCTION CURLOPT_READFUNCTION CURLOPT_PROGRESSFUNCTION

使用么?有没有大佬用过,或者有相关例子的(搜索了很多,没找到)

]]>
各位有什么深入了解 C 语言的书嘛? tag:www.v2ex.com,2023-11-07:/t/989412 2023-11-07T02:54:03Z 2023-11-07T19:00:32Z WilliamColton member/WilliamColton 不熟悉 cmake,请教一下多模块项目的 cmake 写法 tag:www.v2ex.com,2023-10-25:/t/985468 2023-10-25T16:13:37Z 2023-10-26T02:15:43Z cppc member/cppc 项目结构是两个子模块,目录如下

PROJECT-ROOT │ CMakeLists.txt │ ├─app │ │ CMakeLists.txt │ │ │ ├─include │ │ misc.h │ │ │ └─src │ main.cpp │ └─lib-example │ CMakeLists.txt │ ├─include │ lib.h │ └─src lib.cpp 

app 是可执行文件,lib-example 是动态库。app 这个项目需要动态链接到 lib-example,并且使用 lib-example 的头文件。网上有资料介绍类似的用法,我拿过来改了一下,没搞成功。

]]>
大家有没有值得推荐的 c 语言的开源项目,用来学习或者贡献的。 tag:www.v2ex.com,2023-10-22:/t/984327 2023-10-22T15:51:04Z 2023-11-23T21:16:01Z BeiChuanAlex member/BeiChuanAlex 最近在学 c ,希望找个 c 的开源项目看看,最好是网络 tcp/ip 相关的开源项目,其他的也可以,星星多点的,希望各位大佬推荐一下。

ps: 1.如果有点汇编也行,我也想再学学汇编 2.物联网相关的也行,单片机方向 3.nginx 就算了,一口吃不了个胖子,希望能有个总体的代码量少点的。

]]>
求帮忙看一下这个 C 代码为什么会段错误! tag:www.v2ex.com,2023-10-18:/t/983240 2023-10-18T10:44:45Z 2023-10-18T18:56:16Z gooooood member/gooooood typedef struct { int width; int height; int depth; } INFO_T; int Send(int infoNum, const INFO_T *pInfo[]) { INFO_T tInfo; for (int m = 0; m < infoNum; m++) { memcpy(&tInfo, &pInfo[m], sizeof(INFO_T)); printf("info=%d \n", tInfo.width); //这条语句是正常的 printf("info=%d \n", pInfo[m]->width); //按理说这条和上面一条是等价的,但是加入这条语句后就会 core 崩溃 } } ]]> int 型返回值所能表达的内容极限 tag:www.v2ex.com,2023-10-05:/t/979065 2023-10-05T10:20:18Z 2023-10-05T13:22:40Z monkeyNik member/monkeyNik 以前开发的时候经常遇到对外暴露的函数需要提供一些返回值,来表达函数是否出错。

但有时候一个函数可能出现多种错误,所以单纯返回-1 也无法满足需要。有些人的处理方式是增加一个 errno 来标记错误。

但个人感觉还是有些麻烦,而且需要考虑多线程问题。

所以实现了这样一个返回值管理的组件放到了 Melon 库中。

先给出一个例子,大家看看效果吧。

#include "mln_error.h" #define OK 0 #define NMEM 1 int main(void) { char msg[1024]; mln_string_t files[] = { mln_string("a.c"), }; mln_string_t errs[] = { mln_string("Success"), mln_string("No memory"), }; mln_error_init(files, errs, sizeof(files)/sizeof(mln_string_t), sizeof(errs)/sizeof(mln_string_t)); printf("%x %d [%s]\n", RET(OK), CODE(RET(OK)), mln_error_string(RET(OK), msg, sizeof(msg))); printf("%x %d [%s]\n", RET(NMEM), CODE(RET(NMEM)), mln_error_string(RET(NMEM), msg, sizeof(msg))); printf("%x %d [%s]\n", RET(2), CODE(RET(2)), mln_error_string(RET(2), msg, sizeof(msg))); printf("%x %d [%s]\n", RET(0xff), CODE(RET(0xff)), mln_error_string(RET(0xff), msg, sizeof(msg))); return 0; } 

这个程序的执行结果如下:

0 0 [Success] ffffedff 1 [a.c:18:No memory] ffffec01 255 [a.c:19:Unknown Code] ffffeb01 255 [a.c:20:Unknown Code] 

可以看到,通过一个 int 值,我可以获取到错误码、文件、行号甚至是错误码对应的描述。

当然这些内容需要在返回值管理组件初始化的时候给出。

这样,每一份代码的每一个使用RET生成的错误码将是全项目中独一无二的一个负整数。只要有了这个负整数,同时知道了代码的版本,就可以轻松获取到错误的位置和错误内容。

感兴趣的可以看一下Melon 的官方文档了解详情。

]]>
刚刚突然感受到了 C 语言指针的神奇之处 tag:www.v2ex.com,2023-10-01:/t/978506 2023-10-01T14:42:55Z 2023-09-30T22:41:55Z zxCoder member/zxCoder 以前大学学 C 的时候,只是说指针就是地址。

这么多年没再碰 C 语言,刚刚发现,其实指针除了是一个地址,应该还包含“如何去读取这个地址的值”这个东西?

同样的地址,赋值给 int 指针和 char 指针,读取来的东西肯定是不一样的,感觉很有意思。

]]>
开源 C 语言库 Melon 之模块选择性编译 tag:www.v2ex.com,2023-09-26:/t/977358 2023-09-26T10:58:53Z 2023-09-26T07:57:53Z monkeyNik member/monkeyNik Melon是一个跨平台开源 C 语言库,提供了各类开发常用的组件。无第三方软件依赖,开箱即用,且配套中英文文档齐全。

概述

正因为 Melon 提供了非常多的组件,才有了今天这个主题——模块选择性编译。所谓的选择性编译,简单来说就是:想要用什么组件就编译什么组件,无关组件不会编译成库。

这样做有两个目的:

  1. 剔除无关内容,让使用者有选择自由度,只生成有用且精简的库文件
  2. 兼顾无操作系统开发

使用

下面我们就演示一下如何进行模块选择性编译。

第一步:获取 Melon 仓库

git clone https://github.com/Water-Melon/Melon.git 

第二步:进入仓库

cd Melon 

第三步:执行 configure ,我们的选择性编译也是在这一步

./configure --select=rbtree,array 

这里假设我们仅编译红黑树和数组两个组件。

第四步:执行 make

make 

第五步:执行 make install

make install 

如果安装的位置权限不够,则需要

sudo make install 

第三步中--select的参数是以逗号分隔的模块名称,注意不要有空格。

模块名称

在 Melon 官方文档中,每个组件的文档内,都给出了对应的模块名称。

还有一种简单的办法去获取模块名。在 Melon 中,几乎每个模块都有一对.c 和.h 文件。当我们需要用到某个模块,一般都是 include 那个模块的头文件。而头文件的命名一般是mln_<module>.h,这里的module部分就是模块名称(<>只是用来突出模块名称)。

实例

下面内容为笔者在虚机上执行上述命令进行选择性编译安装的终端输出内容。

$ git clone https://github.com/Water-Melon/Melon.git Cloning into 'Melon'... remote: Enumerating objects: 6095, done. remote: Counting objects: 100% (1281/1281), done. remote: Compressing objects: 100% (480/480), done. remote: Total 6095 (delta 909), reused 1131 (delta 793), pack-reused 4814 Receiving objects: 100% (6095/6095), 3.22 MiB | 1.02 MiB/s, done. Resolving deltas: 100% (4417/4417), done. $ cd Melon nik@nik:~/test/Melon$ ./configure --select=rbtree,array Installation Path [/usr/local/melon] Melang script Path [/usr/local/lib/melang] Melang dylib Path [/usr/local/lib/melang_dynamic] Configure done! Melon$ make test -d objs || mkdir objs test -d lib || mkdir lib cc -Iinclude -c -Wall -Werror -O3 -fPIC -o objs/mln_rbtree.o src/mln_rbtree.c cc -Iinclude -c -Wall -Werror -O3 -fPIC -o objs/mln_array.o src/mln_array.c cc -o lib/libmelon.so objs/mln_rbtree.o objs/mln_array.o -Wall -lpthread -Llib/ -ldl -shared -fPIC ar -r lib/libmelon.a objs/mln_rbtree.o objs/mln_array.o ar: creating lib/libmelon.a Melon$ sudo make install test -d /usr/local/lib/melang || mkdir -p /usr/local/lib/melang test -d /usr/local/melon || mkdir -p /usr/local/melon cp -fr lib /usr/local/melon cp -fr include /usr/local/melon test -d /usr/local/melon/conf || cp -fr conf /usr/local/melon test -d /usr/local/lib/melang/trace || cp -fr trace /usr/local/lib/melang 

可以看到,只有红黑树和数组的源文件被编译和打包成库了。

]]>
这年头搞 c 还有前途吗 看看我的 leetcode 第 151 题目 反转字符串 tag:www.v2ex.com,2023-09-15:/t/974127 2023-09-15T07:23:46Z 2023-09-17T04:51:49Z zzzkkk member/zzzkkk #include "stdio.h" #include "stdlib.h" #include "string.h" char * reverseWords(char * s){ char * start, * start2; start = s; while(*start != '\0'){ start ++; } -- start; char s2[strlen(s)]; char * s2p = s2; while(start > s){ while(*start == ' ') start --; while(*start != ' ' && start >= s) start --; start2 = start; ++start2; while(*start2 != ' '){ *s2p++ = *start2++; } *s2p++ = ' ' ; } *s2p = '\0'; start = s; s2p = s2; while(*s2p!='\0') *start++ = *s2p++; *start = '\0'; return s; } int main(int argc, char ** argv){ char s[] = "abc edf ghi lkm opq rst "; printf("%s\n",reverseWords(s)); }

在 linux 机器上能编译运行正确 leetcode 提示==20==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000020 at pc 0x563392652f73 bp 0x7ffefb8e0c40 sp 0x7ffefb8e0c30 READ of size 1 at 0x602000000020 thread T0

我是不是太贱了?

]]>
百变开源 C 语言库日志模块 tag:www.v2ex.com,2023-09-12:/t/973160 2023-09-12T14:46:21Z 2023-09-12T10:45:21Z monkeyNik member/monkeyNik 本文向大家介绍一个名为Melon的开源 C 语言库的日志模块。

简述 Melon

Melon 是一个包含了开发中常用的各类组件的开源 C 语言库,支持 Linux 、MacOS 、Windows 系统,可用于服务器开发亦可用于嵌入式开发,无第三方软件依赖,安装简单,开箱即用,中英文文档配套齐全。

日志模块

日志是 Melon 库提供的通用组建之一,在介绍其特性前,我们先来看一下它的简单使用示例。

#include "mln_log.h" int main(int argc, char *argv[]) { mln_log(debug, "This will be outputted to stderr\n"); mln_log_init(NULL); mln_log(debug, "This will be outputted to stderr and log file\n"); return 0; } 

可以看到,使用与我们常用的printffprintf很相似。

同时,从这个例子中也可以看到这个日志模块允许在未初始化情况下使用。在未初始化时,这些日志都将被输出至stderr中。

当然,我们也可以使用mln_log_init对日志模块进行初始化。初始化后将获得两种能力:

  1. 日志会跟据不同等级显示不同的颜色
  2. 日志内容既会被输出到 stderr ,也会被输出到 Melon 配置中指定的日志文件中(关于配置,请参见官方手册)。

进阶

显然,我们的日志不仅仅可以做这些事情。

下一步我们将讨论如何将日志内容做自定义处理,我们将介绍日志回调函数。

这里涉及到两个日志模块的函数:

我们看一下简单的例子,我们对上面的例子进行一顿改造:

#include <stdio.h> #include "mln_log.h" mln_logger_t old = NULL; static void mylogger(mln_log_t *log, mln_log_level_t level, const char *file, const char *func, int line, char *fmt, va_list args) { printf("%s:%s:%d: %s\n", file, func, line, fmt); if (old != NULL) old(log, level, file, func, line, fmt, args); } int main(int argc, char *argv[]) { mln_log(debug, "This will be outputted to stderr\n"); mln_log_init(NULL); old = mln_log_logger_get(); mln_log_logger_set(mylogger); mln_log(debug, "This will be outputted to stderr and log file\n"); return 0; } 

我们增加了一个自己的日志处理函数名为mylogger,它将截获日志的内容及其关联信息,使用printf将这些信息输出到标准输出(stdout)中。然后再使用原有的日志处理函数将日志输出到终端的stderr和日志文件中。

这样,我们就可以随意地对日志内容进行处理了。

结语

Melon 的日志模块允许开发者对其进行任意扩展来满足自己的需求。

另外,要补充一点,这个mln_log是允许多线程使用的。并且对于x86 架构,在不调用mln_log_init对日志进行初始化的情况下,这个函数就是多线程可用的。

感谢阅读!

]]>
最好用的 C 语言 JSON 解析器 tag:www.v2ex.com,2023-09-11:/t/972885 2023-09-11T16:46:43Z 2023-09-11T15:45:43Z monkeyNik member/monkeyNik 本文介绍开源 C 语言库Melon的 JSON 解析器。

相信很多读者都听说过甚至使用过 cJSON 开源库。那么本文就拿 cJSON 与 Melon 的 JSON 组件进行对比。

下面我们就来一起看一看。

编码 Encode

假设我们要构建如下 JSON:

{ "name": "Awesome 4K", "resolutions": [ { "width": 1280, "height": 720 }, { "width": 1920, "height": 1080 }, { "width": 3840, "height": 2160 } ] } 

那么,我们先看一下cJSON的构建代码:

#include <stdio.h> #include <cjson/cJSON.h> //NOTE: Returns a heap allocated string, you are required to free it after use. char *create_monitor_with_helpers(void) { const unsigned int resolution_numbers[3][2] = { {1280, 720}, {1920, 1080}, {3840, 2160} }; char *string = NULL; cJSON *resolutiOns= NULL; size_t index = 0; cJSON *mOnitor= cJSON_CreateObject(); if (cJSON_AddStringToObject(monitor, "name", "Awesome 4K") == NULL) { goto end; } resolutiOns= cJSON_AddArrayToObject(monitor, "resolutions"); if (resolutiOns== NULL) { goto end; } for (index = 0; index < (sizeof(resolution_numbers) / (2 * sizeof(int))); ++index) { cJSON *resolution = cJSON_CreateObject(); if (cJSON_AddNumberToObject(resolution, "width", resolution_numbers[index][0]) == NULL) { goto end; } if (cJSON_AddNumberToObject(resolution, "height", resolution_numbers[index][1]) == NULL) { goto end; } cJSON_AddItemToArray(resolutions, resolution); } string = cJSON_Print(monitor); if (string == NULL) { fprintf(stderr, "Failed to print monitor.\n"); } end: cJSON_Delete(monitor); return string; } int main(void) { char *p; p = create_monitor_with_helpers(); printf("%s\n", p); return 0; } 

下面,我们一起看下使用Melon 的 JSON 组件的代码:

#include <stdio.h> #include "mln_json.h" #include "mln_log.h" static mln_string_t *generate(void) { mln_json_t j; mln_string_t *ret; mln_json_init(&j); mln_json_generate(&j, "{s:s,s:[{s:d,s:d},{s:d,s:d},{s:d,s:d}]}", \ "name", "Awesome 4K", "resolutions", "width", 1280, "height", 720, \ "width", 1920, "height", 1080, "width", 3840, "height", 2160); ret = mln_json_encode(&j); mln_json_destroy(&j); return ret; } int main(void) { mln_string_t *p; p = generate(); mln_log(none, "%S\n", p); return 0; } 

解码 Decode

假设我们有如下 JSON:

{ "name": "Awesome 4K", "resolutions": [ { "width": 1280, "height": 720 } ] } 

下面一起来看下解码,还是先上cJSON的代码:

#include <stdio.h> #include <cjson/cJSON.h> /* return 1 if the monitor supports full hd, 0 otherwise */ int supports_full_hd(const char * const monitor) { const cJSON *resolution = NULL; const cJSON *resolutiOns= NULL; cJSON *monitor_json = cJSON_Parse(monitor); if (monitor_json == NULL) return -1; resolutiOns= cJSON_GetObjectItemCaseSensitive(monitor_json, "resolutions"); cJSON_ArrayForEach(resolution, resolutions) { cJSON *width = cJSON_GetObjectItemCaseSensitive(resolution, "width"); return width->valuedouble; } cJSON_Delete(monitor_json); return -1; } int main(void) { char p[] = "{\"name\":\"Awesome 4K\",\"resolutions\":[{\"width\":1280,\"height\":720}]}"; int i = supports_full_hd(p); printf("%d\n", i); return 0; } 

接下来是Melon 的 JSON 组件的解码代码:

#include <stdio.h> #include "mln_json.h" #include "mln_log.h" static int handler(mln_json_t *j, void *data) { return (int)M_JSON_GET_DATA_NUMBER(j); } static int parse(mln_string_t *p) { mln_json_t j; mln_string_t exp = mln_string("resolutions.0.width"); mln_json_decode(p, &j); return mln_json_parse(&j, &exp, handler, NULL); } int main(void) { mln_string_t p = mln_string("{\"name\":\"Awesome 4K\",\"resolutions\":[{\"width\":1280,\"height\":720}]}"); int i = parse(&p); mln_log(none, "%d\n", i); return 0; } 

结语

Melon 的 JSON 组件主要提供了如下四个函数来便于使用者构建和解析 JSON:

Melon 的 JSON 组件提供了易于阅读和使用的函数接口,更易于开发者对项目的维护。

欢迎大家来试用开源 C 语言库 Melon 。

Github: https://github.com/Water-Melon/Melon

]]>
请教下 mac 环境下,我这几行代码的问题在哪里 tag:www.v2ex.com,2023-09-11:/t/972763 2023-09-11T07:59:34Z 2023-09-11T09:17:42Z awanganddong member/awanganddong #include <stdio.h> #include <pthread.h> #include <semaphore.h> sem_t mutex; void *my_thread() { while (1) { //判断信号量是否大于 0 ,如果大于 0 ,执行减一操作,并继续 sem_wait 之后的代码;如果当前 sem 等于 0 ,阻塞等待直至大于零 // (大于零之后执行减一操作,继续 sem_wait 之后的代码) printf("测试数据 1"); sem_wait(&mutex); //信号量值加一,直接返回; printf("测试数据 2"); sem_post(&mutex); printf("测试数据 3"); break; } return 0; } int main() { pthread_t thread_1, thread_2; //声明线程 ID // sem_open()创建一个新的 POSIX 信号量或打开一个现有的信号量。信号灯由名称标识。有关名称构造的详细信息,请参见 sem_overview(7)。 // oflag 参数指定用于控制调用操作的标志。 (可以通过包含来获得标志值的定义。)如果在 oflag 中指定 O_CREAT ,则如果信号灯尚不存在,则会创建该信号灯。 // 信号量的所有者(用户 ID)设置为调用过程的有效用户 ID 。组所有权(组 ID)设置为调用过程的有效组 ID 。如果在 oflag 中同时指定了 O_CREAT 和 O_EXCL , // 则如果已经存在具有给定名称的信号灯,则将返回错误。 // 如果在 oflag 中指定 O_CREAT ,则必须提供两个附加参数。mode 参数指定要放置在新信号量上的权限,就像 open(2)一样。 // (可以通过包含。获得权限位的符号定义。)权限设置针对进程 umask 进行掩码。应该向将访问信号量的每个用户类别都授予读取和写入权限。 // value 参数指定新信号量的初始值。如果指定了 O_CREAT ,并且已经存在具有给定名称的信号灯,则将忽略 mode 和 value mutex = *sem_open("mutex", O_CREAT, S_IRWXU, 1); // Name 用于标识信号量的名字 // Oflag 被设置为 O_CREAT 用来创建一个信号量(如果和 0_EXCL 一起,当这个信号量已经存在时候这个调用将会失败) // mode_t 控制新的信号量的访问权限 // Value 指定信号量的初始化值 // ( 1 ) tidp:事先创建好的 pthread_t 类型的参数。成功时 tidp 指向的内存单元被设置为新创建线程的线程 ID 。 // ( 2 ) attr:用于定制各种不同的线程属性。APUE 的 12.3 节讨论了线程属性。通常直接设为 NULL 。 // ( 3 ) start_rtn:新创建线程从此函数开始运行。无参数是 arg 设为 NULL 即可。 // ( 4 ) arg:start_rtn 函数的参数。无参数时设为 NULL 即可。有参数时输入参数的地址。当多于一个参数时应当使用结构体传入。(以下举例) //向线程函数传递参数 pthread_create(&thread_1, NULL, &my_thread, NULL); pthread_create(&thread_2, NULL, &my_thread, NULL); //以阻塞的方式等待 thread 指定的线程结束。当函数返回时,被等待线程的资源被收回。 // 如果线程已经结束,那么该函数会立即返回。并且 thread 指定的线程必须是 joinable 的 pthread_join(thread_1, NULL); pthread_join(thread_2, NULL); sem_unlink("mutex"); return 0; } ``` 报错是这个 zsh: segmentation fault ./a.out ]]> C 语言开源库推荐,嵌入式/服务器开发均可用 tag:www.v2ex.com,2023-08-09:/t/963905 2023-08-09T15:33:28Z 2023-10-03T16:56:44Z monkeyNik member/monkeyNik C 语言开源库推荐,嵌入式/服务器开发均可用

给大家推荐一个自己开发和维护至今 9 年的 C 语言开源项目——Melon 。

这个项目中实现了各种我以往工作或者私人项目中用到的一些公共组件,包含但不限于:数据结构、算法,还包含各类常用组件,如:内存池,各类线程模型、多进程模型等等。

这个库没有第三方依赖,所以安装简单,开箱即用,中英文文档齐全,每个组件均配有可执行的示例。

操作系统环境:完整支持 UNIX/Linux 环境,Windows 下除框架功能暂不可用,其他组件均可用。

Docker:目前由于无法访问 docker hub ,所以 docker 镜像尚无法跟随 master 分支最新代码。

Webassembly:支持使用 emcc ,将 Melon 编译为 wasm 静态库。

支持服务器开发,也可以支持嵌入式交叉编译。

在 V2EX 也发过很多篇关于这个库的使用文章,感兴趣的朋友可以翻阅一下本号历史。最近也发布过一个短视频来介绍其中的组件。

GitHub: https://github.com/Water-Melon/Melon

Gitee 镜像站: https://gitee.com/melon-c/Melon

B 站视频: [ C 语言必备开源库——红黑树使用]

]]>
ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86