加载失败
讨论围绕 Zig 与 C++ 互操作的实际细节展开,核心问题是如何在不暴露完整类型定义的情况下在不同语言间传递或嵌入类型。评论基于 ABI(应用二进制接口)与调用约定指出相同大小/对齐的类型仍可能因寄存器使用(如 xmm registers)而有不同传参行为,因而按值传递存在风险。有人分享了在 Rust(RediSearch 项目)中通过 opaque sized type 与 cbindgen(Rust->C 头生成工具)传递大小/对齐的做法,并讨论了 cbindgen 的限制及由硬编码尺寸带来的编译器绑定问题。历史上 Objective-C++ 的经验被提及作为语言混合导致工程复杂性的警示。
评论指出在跨语言嵌入类型时,通常只需要知道类型的大小(size)和对齐(alignment),而不必暴露字段定义,这样可以在宿主侧为其分配存储并以指针传递。与此同时,多位评论者强调相同大小/对齐的类型在 ABI 层面可能仍有不同的传参规则:例如含两个 float 的 struct 往往按浮点寄存器(xmm registers)传递,而同样占 8 字节的 char[8] 则不会,因此按值传递会产生不兼容。实践建议是避免按值跨语言传递这类类型,而是嵌入并传指针(或传向 shared_ptr 的指针),作为介于完全不透明类型与按值语义之间的折衷。评论还引用了相关 GitHub 讨论作为背景,并指出编译器选项(如 -fno-trapping-math)会改变生成代码,因为上半部分寄存器中的“垃圾”可能引发浮点异常,从而影响 mov 指令的产生。
RediSearch 的 Rust 移植采用了一种 "opaque sized type" 模式:在 Rust 侧声明不透明类型并通过 cbindgen 将其大小与对齐信息导出到 C 头,这样 C 侧虽然看不到字段,但仍能在栈上为该类型分配空间。该方法使得一些传统上不被认为 FFI-safe 的值可以跨语言边界使用,但实现上比较别扭,因为 cbindgen 不支持 const-generic 表达式且宏展开在稳定版 Rust 中有限制。另一位评论者补充说这种做法通常依赖 C ABI 且尺寸是硬编码的,因此容易被特定的 C++ 编译器/ABI 绑定,降低可移植性。总体结论是实用但脆弱:用方便的大小/对齐暴露换来的是对编译器和 ABI 细节的依赖。
有人表示这类互操作问题会让人想起 Objective-C++ 的“噩梦”经验,另有人回忆自己用 Objective-C++ 把 C++ 库引入 Cocoa 应用并对此表示怀念。回复指出 Objective-C++ 常被视为两种语言的不光彩焊接(an unholy welding),主要作为让 Objective-C 应用复用 C++ 代码的过渡层或作为跨平台 C++ 到 Cocoa UI 的薄包装层,而非长期的主要开发范式。这个历史例子被用来提醒:语言混用经常带来工程上的复杂性和维护成本,Zig/C++ 互操作会遇到类似的折衷与痛点。
ABI: ABI(Application Binary Interface,应用二进制接口):定义二进制层面函数参数/返回值的传递方式、寄存器与栈的使用、对齐和数据布局,是跨编译单元或语言互操作时最关键的兼容约定。
FFI: FFI(Foreign Function Interface):不同编程语言之间调用函数和共享数据的接口集合,涉及调用约定、数据表示与安全边界等问题。
opaque sized type: opaque sized type:一种在调用方只知晓大小与对齐、不可见内部字段的类型,用于在 FFI 边界上分配存储但隐藏内部布局细节,常用于桥接不安全或非标准布局的数据。
cbindgen: cbindgen:一个 Rust 社区工具,用于从 Rust 代码生成 C 头文件,在跨语言互操作中常被用来把 Rust 的类型信息(如大小/对齐)导出给 C/C++。
xmm registers: xmm registers:x86 架构下的 128-bit SSE/AVX 浮点/向量寄存器,编译器在 ABI 约定下可能用它们来按值传递浮点或向量类型,从而影响跨语言的参数传递语义。