【问题标题】:Is repr(C) a preprocessor directive?repr(C) 是预处理器指令吗?
【发布时间】:2021-04-23 20:00:59
【问题描述】:

我已经看到一些 Rust 代码库使用 #[repr(C)](这就是它的名字吗?),但是,我找不到太多关于它的信息,但它设置了类型布局在内存中以与“C”相同的布局。

这是我想知道的:这是一个仅限于编译器而不是语言本身的预处理器指令(即使没有任何其他用于 Rust 的编译器前端),以及为什么 Rust 甚至有内存布局与Cs不同? (只是我从来不需要用另一种语言来做这件事)。

这是一个很好的例子来说明我的意思:如果有人为 Rust 创建另一个编译器,他们是否需要实现这个宏,或者它是编译器特定的东西?

【问题讨论】:

  • github.com/rust-lang/reference/blob/… Is repr(C) a preprocessor directive? Rust 有预处理器?你心目中的“预处理器”是什么样的?
  • @KamilCuk 我认为我在这里使用了错误的术语,如果您知道正确的术语,请务必纠正我,但我的意思是编译器前端会预处理应用此宏的结构这个布局,然后进行编译/优化。
  • 当然可以,这就是编译器所做的——处理文本然后生成代码。
  • are they required - 好吧,没有“防锈警察”会来惩罚你不执行它....
  • 只是一个小旁注,#pragmais part of C。虽然#pragma 是预处理指令,但编译器必须在预处理器之后做出决定,因为函数中的#pragma STDC FENV_ACCESS ON 只影响该函数,因此它需要知道函数何时结束,这不是预处理器所做的。跨度>

标签: memory rust


【解决方案1】:

#[repr(C)] 不是预处理器指令,因为 Rust 不使用预处理器 1。这是一个attribute。 Rust 没有完整的规范,但是 repr 属性 is mentioned in the Rust reference,所以它绝对是语言的一部分。在实现方面,属性的解析方式与所有其他 Rust 代码相同,并且存储在相同的 AST 中。 Rust 没有“属性传递”:属性是语言的实际部分。如果其他人要实现 Rust 编译器,他们需要实现 #[repr(C)]

此外,#[repr(C)] 无法在没有一些编译器魔法的情况下实现。在没有 #[repr(...)] 的情况下,Rust 编译器可以随意排列 struct/enum 的字段(他们确实利用这一点进行优化!)。

Rust 确实有充分的理由使用它自己的内存布局。如果编译器与struct 在源代码中的编写方式无关,他们can do optimisations like 不会存储从未读取过的struct 字段,重新排序字段以获得更好的性能,enum 标记池2 ,并在结构中的整个NonZero*s 中使用备用位来存储数据(最后一个尚未发生,但将来可能会发生)。但主要原因是 Rust 有一些在 C 中没有意义的东西。例如,Rust 有零大小的类型(如 ()[i8; 0]which can't exist in Ctrait vtables、enum s 带有字段、泛型类型,所有这些在尝试将它们转换为 C 时都会导致问题。


1 好的,如果你真的想的话,你可以使用带有 Rust 的 C 预处理器。请不要。

2 例如,enum Food { Apple, Pizza(Topping) } enum Topping { Pineapple, Mushroom, Garlic } 只能存储在 1 个字节中,因为只有 4 个可能的 Food 值可以创建。

【讨论】:

  • 这是一个优秀的解释。我很惊讶 Rust 编译器已经在做这样的优化。我不能保证你不使用 C 预处理器;)
【解决方案2】:

这是什么?

它不是一个宏,它是一个属性。

这本书有一个关于宏是什么的good chapter,并提到有“类似属性的宏”:

宏一词指的是 Rust 中的一系列功能:带有 macro_rules 的声明性宏!以及三种过程宏:

  • 自定义#[derive] 宏,用于指定使用结构和枚举使用的派生属性添加的代码
  • 定义可用于任何项目的自定义属性的类属性宏
  • 类似于函数的宏,看起来像函数调用,但对指定为其参数的标记进行操作

类似属性的宏是您可以使用的like 属性。例如:

#[route(GET, "/")]
fn index() {}

看起来确实像 repr 属性不是吗 ?

那么什么是属性呢?

幸运的是,Rust 有很多资源,比如 rust-by-example which includes:

属性是应用于某些模块、板条箱或项目的元数据。此元数据可用于/用于:

  • 代码的条件编译
  • 设置 crate 名称、版本和类型(二进制或库)
  • 禁用 lints(警告)
  • 启用编译器功能(宏、全局导入等)
  • 链接到国外图书馆
  • 将函数标记为单元测试
  • 标记将成为基准测试一部分的函数

当您需要更深入地了解某些内容时,您通常会查看 rust 参考。 (chapter for attributes)

致编译器作者:

如果您要编写一个 rust 编译器,并且想要支持标准库或其他 crate 之类的东西,那么您将 100% 需要实现这些。因为图书馆使用这些并需要它们。

否则我猜你可能会想出一个你的编译器支持的 rust 子集。但是大多数人不会使用它..

为什么 rust 不只使用 C 布局?

nomicon explains 例如,为什么 rust 需要能够重新排序结构的字段。出于节省空间和提高效率的原因。除其他外,它与泛型和单态化有关。在repr(C) 中,结构体的字段必须与定义的顺序相同。

C 表示是为双重目的而设计的。一个目的是创建可与 C 语言互操作的类型。第二个目的是创建可以可靠地执行依赖于数据布局的操作的类型,例如将值重新解释为不同的类型。

【讨论】:

    猜你喜欢
    • 2021-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-23
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    相关资源
    最近更新 更多