【问题标题】:Is there a C equivalent to Rust's NonNull<T>::dangling() pointer instantiation?是否有与 Rust 的 NonNull<T>::dangling() 指针实例化等效的 C 语言?
【发布时间】:2020-06-03 07:38:17
【问题描述】:

如果存在,则应满足以下性质:

  • 类型为void *
  • 不需要实例化“虚拟对象”来充当地址
  • 保证不等于NULL
  • 可以在不调用未定义行为的情况下构造
  • 使用符合标准的编译器,无需非标准扩展

起初我以为我可以做(NULL + 1)(void *)1 之类的事情,但这些似乎有问题。前者在NULL 上使用指针算法,我认为这是未定义的行为。第二个依赖于NULL没有物理地址1的事实。(即(void *)0 == (void *)1完全有可能)

【问题讨论】:

  • 不,在标准 C 中没有类似的东西。请问您为什么需要它?你试图用它解决什么问题?
  • @Stargateur 不是。是IDB
  • 值得一提的是,Rust 的 dangling() 相当于 (void*)alignof(T) 以确保指针正确对齐,同时尽可能靠近 0 地址。
  • @Stargateur 将整数转换为指针是实现定义的行为。自然地,因为地址映射是特定于实现的。但是,在具有虚拟地址空间的托管系统上这样做肯定是有问题的做法。在直接寻址物理内存的较低级别系统上,这完全没问题。
  • @Someprogrammerdude 我想减少结构的内存占用,因为我有很多存储在数组中。 Rust 能够压缩结构,因为知道NonNull 永远不能为空,因此Option&lt;NonNull&gt;Option&lt;SomeStructContainingNonNull&gt; 不需要额外的“标志字节”。基本上,想想 C 中的标记联合,但不需要标记。

标签: c pointers void-pointers dangling-pointer


【解决方案1】:

任何 void 指针都能满足您的所有要求。

只要您确定哪些地址有效并在特定系统上获取,您就可以手动创建这样的指针:

void* dangling = (void*)0x12345678; // an address which you know for sure isn't taken

这是完全符合标准的。结果是实现定义的,因为分配的有效地址和对齐等内容是系统特定的。

至于这对你有什么好处,我不知道。当指针未设置为指向分配的地址时,应使用空指针。


起初我以为我可以执行 (NULL + 1) 或 (void *)1 之类的操作,但这些似乎有问题。前者在 NULL 上使用指针算术,我认为这是未定义的行为。

您将空指针与空指针常量NULL 混淆了。 NULL 可以扩展为 0(void*)0

  • 如果您进行算术运算0 + 1,您只需得到一个整数常量表达式1。如果需要,可以将其转换为指针,与上面的 impl.defined 行为相同,实际上等同于 (void*)1
  • 如果您进行算术运算(void*)0 + 1,那么代码将无法编译,因为您无法对 void 指针进行算术运算。如果您对未指向已分配数组的指针进行指针运算,则它是 UB。

【讨论】:

  • 至于这对你有什么好处,我不知道。 => 在 Rust 中,NonNull 保证它永远不会为 null,这允许使用 null 值代表另一个国家。例如,标记的联合 Option&lt;NonNull&lt;T&gt;&gt; 具有指针的大小,空字节模式代表None 替代方案,而任何非空字节模式代表Some(NonNull&lt;T&gt;)。当需要紧凑的内存表示时,这种技巧在 C 中可能是可取的。
  • 所以,猜测一下,您不是在使用 JavaScript 引擎(使用 NaN 标记将指针放入 double 中),也不是在使用 clang/LLVM(使用最低的指针位来存储元信息)。你认为无用的东西,其他人发现确实有价值,因为内存往往是一个瓶颈。
  • @Stargateur:dangling的重点是创建一个非NULL值,所以指出NULL可以用作None并没有帮助。
  • 我正在使用 C - 以及通常内置的 JavaScript 引擎这简直是糟糕的设计 ——虽然在大多数情况下都是正确的,但这种笼统的陈述不能适用于所有情况。出于类似的原因,C 甚至还有位域。
  • @Shepmaster C 有位域是什么原因?用于创建不能可靠地用于任何其他目的的任意行为二进制 blob,而不是作为以奇怪或未知方式分配的一大块内存优化不佳的布尔值?因为他们就是这样。它们肯定不是为了摆弄空指针的内部表示而创建的,这是肯定的。
【解决方案2】:

NonNull::dangling() 存在于 Rust 中,以便能够在给它实际值之前临时初始化一个 NonNull 值。您不能使用 null 作为临时值,因为它是 NonNull,它会呈现未定义的行为。

例如,这个完全安全(我猜)可自我引用的example 需要NonNull::dangling()

struct SelfRef {
    myself: NonNull<SelfRef>,
    data: String,
}

impl SelfRef {
    fn new(data: String) -> Pin<Box<SelfRef>> {
        let mut x = Box::pin(SelfRef {
            myself: NonNull::dangling(),
            data,
        });
        x.myself = unsafe { NonNull::new_unchecked(x.as_mut().get_unchecked_mut()) };
        x
    }
}

关于您在 C 中等同于 NonNull::dangling() 的问题是,在 C 中没有 NonNull,因此对于这些类型的临时初始化,您可以 NULL 或将其统一化,直到您获得适当的值.

struct SelfRef {
    SelfRef *myself;
    //...
};

struct SelfRef *new_selfref() {
    struct SelfRef *x = malloc(sizeof(struct SelfRef));
    //Here x->myself is uninitialized, that is as good as dangling()
    x->myself = x;
    return x;
}

也就是说,我确信NonNull::dangling 除了临时初始化自引用结构之外还有其他用途。对于那些您可能实际上需要等效的 C 代码的人。等效的 C 代码将是(以宏形式,因为它需要一个类型作为参数):

#define DANGLING(T) ((T*)alignof(T))

即在符合给定类型的对齐方式的同时尽可能接近零的指针。这个想法是,在大多数架构中,NULL 指针实际上位于地址 0,并且前几 KB 从未映射,因此运行时可以捕获 NULL 取消引用。而且由于最大对齐要求通常只有几个字节,这永远不会指向有效内存。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-19
    相关资源
    最近更新 更多