C

43 文章

发布 CLI、crate 和版本管理

4 分钟

C 项目交付给别人用时,问题通常不在“源码能不能编译”,而在这些细节:

工具怎么安装到 PATH
库文件和头文件放在哪里
用户该链接哪个版本
升级会不会破坏已有代码
公开 API 改了算不算兼容
二进制 ABI 能不能长期稳定

Rust 也要处理这些问题。只是发布对象分成了两类:

阅读更多

release、交叉编译和 binary size

5 分钟

C 项目交付前,通常不会只看“能编译”。

还要看:

是不是 release 优化
目标架构和 ABI 对不对
链接的是目标系统的库还是宿主机库
二进制有没有带多余符号
动态库依赖目标系统有没有
体积是否符合设备限制
崩溃后还能不能符号化

Rust 交付 CLI、系统库或 C/Rust 边界产物时,也要回答这些问题。Cargo 简化了构建命令,但没有取消交付检查。

阅读更多

workspace、feature 和构建配置

4 分钟

C 项目长大以后,通常会出现这些东西:

多个静态库和命令行工具
公共头文件和内部头文件
Makefile 子目录
CONFIG_XXX / #ifdef 开关
Debug/Release 编译选项
目标平台差异

Rust 项目也会遇到同样的问题,只是工具名字不同:

workspace -> 管多个 crate
crate     -> 一个库或一个可执行目标的边界
feature   -> 可选能力和可选依赖
profile   -> debug/release 等构建参数
cfg       -> 平台和条件编译

这一篇不讲 Cargo 全部语法,只讲写库、CLI 和 C/Rust 边界时最常用的工程组织方式。

阅读更多

build.rs、link 和 pkg-config

4 分钟

写 FFI 时,声明 C 函数只是第一步:

unsafe extern "C" {
    fn sensor_read(out: *mut u8, len: usize) -> i32;
}

这只告诉 Rust:有一个 C ABI 函数叫 sensor_read。

但链接器还要知道:

这个函数在哪个库里?
库文件在哪个目录?
还需要链接哪些依赖库?
交叉编译时用的是目标系统的库,还是宿主机的库?

C 项目里这些通常由 Makefile、CMake、pkg-config、sysroot 和交叉工具链处理。Rust 项目里,对应工具是 #[link]、build.rs 和 build dependency。

阅读更多

静态库、动态库和 ABI

5 分钟

前面几篇讲的是函数级 FFI:extern "C"、字符串、内存归属、回调和 void *ctx。

真正交付给 C 项目时,还要回答另一个问题:

C 工程到底链接什么文件?
头文件声明哪些符号?
这些符号的 ABI 能不能长期稳定?
升级 Rust 代码后,C 侧要不要改?

C 开发者熟悉 .a、.so、.h 和 ABI。Rust 交付给 C 使用时,也要回到这些概念,而不是把 Cargo 内部模型暴露给 C。

阅读更多

FFI 回调和 void *ctx

4 分钟

C 里的回调接口通常长这样:

typedef void (*event_cb_t)(void *ctx, int event);

int device_register_callback(struct device *dev, event_cb_t cb, void *ctx);
void device_unregister_callback(struct device *dev);

这个设计很常见:函数指针负责执行,ctx 负责保存调用方环境。

Rust 内部可以用 closure 表达“代码 + 环境”。但跨 C ABI 时,捕获环境的 closure 不能直接变成 C 函数指针。C 只认识固定 ABI 的函数地址和一个 void *ctx。

阅读更多

C 字符串、内存归属和错误返回

4 分钟

写 FFI 时,extern "C" 只是入口。

真正容易出事故的地方通常是三类:

C 字符串到底是不是有效的 NUL 结尾字符串
返回的指针由谁分配、由谁释放、用哪个函数释放
错误是整数码、输出参数,还是错误文本

C 代码里这些靠接口文档和团队约定维护。Rust 可以把一部分规则包进类型,但跨语言边界本身仍然要设计清楚。

阅读更多

并发:Send、Sync、Arc 和 Mutex

5 分钟

C 里写线程并发,基本工具很直接:

pthread_create(&tid, NULL, worker, ctx);
pthread_mutex_lock(&lock);
/* modify shared state */
pthread_mutex_unlock(&lock);

这些工具能写出可靠系统,但规则主要靠人维护:

  • ctx 指向的数据能不能跨线程使用
  • 谁拥有这块数据
  • 多个线程是否同时读写
  • 有没有忘记加锁
  • 锁保护的到底是哪几个字段
  • 回调和线程退出时资源是否还活着

Rust 没有让并发自动变简单。它做的是把一部分线程安全要求放进类型系统:

阅读更多

closure 和回调

4 分钟

C 里写回调,最常见的形状是函数指针加上下文指针:

typedef void (*event_cb_t)(void *ctx, int event);

void register_event(event_cb_t cb, void *ctx);

cb 是要调用的函数,ctx 是调用时带回来的环境。这个模式很成熟,但也有老问题:

  • ctx 的真实类型靠约定
  • ctx 活多久靠约定
  • 回调会不会修改环境靠约定
  • 回调会不会只调用一次靠约定
  • 多线程调用时是否安全靠约定

Rust 的 closure 解决的是同一个问题:把“要执行的代码”和“它需要的环境”放在一起。

阅读更多

iterator

4 分钟

C 程序员写遍历,第一反应通常是索引或指针:

for (size_t i = 0; i < len; i++) {
    sum += data[i];
}

或者:

for (uint8_t *p = buf; p < buf + len; p++) {
    *p ^= key;
}

Rust 当然也能写循环,但更常用的模型是 iterator。它不是“高级语法糖”,而是把遍历过程变成一个可以传递、组合、消费的值。

阅读更多