如果存在,它应该满足以下属性:
- 具有类型
void *
- 不需要实例化"虚拟对象"作为地址
- 保证不等于
NULL
- 可以在不调用未定义行为的情况下构造
- 与符合标准的编译器配合使用,无需非标准扩展
起初我以为我可以做一些像(NULL + 1)
或(void *)1
的事情,但这些似乎有问题。前者在NULL
上使用指针算法,我认为这是未定义的行为。第二个依赖于NULL
没有物理地址 1 的事实。(即完全有可能(void *)0 == (void *)1
)
任何 void 指针都可以满足您的所有要求。
只要您确定哪些地址是有效的,并且在特定系统上采用,您就可以手动创建这样的指针:
void* dangling = (void*)0x12345678; // an address which you know for sure isn't taken
这完全符合标准。结果是实现定义的,因为分配的有效地址和对齐方式等内容是特定于系统的。
至于这对你有什么好处,我不知道。当指针未设置为指向分配的地址时,应使用 Null 指针。
起初我以为我可以做类似 (NULL + 1) 或 (void *)1 之类的事情,但这些似乎有问题。前者在 NULL 上使用指针算法,我认为这是未定义的行为。
您正在将空指针与空指针常量NULL
混淆。NULL
可以扩展到0
或(void*)0
.
- 如果你做算术
0 + 1
你只需得到一个整数常量表达式1
。如果需要,可以将其转换为指针,与上述相同的impl.defined行为,实际上等同于(void*)1
。 - 如果你做算术
(void*)0 + 1
那么代码将无法编译,因为你不能在空指针上做算术。如果您在指针上执行指针算术而不指向分配的数组,则它是 UB。
NonNull::dangling()
存在于Rust 中,能够在给它实际值之前临时初始化一个NonNull
值。不能将null
用作临时,因为它是NonNull
,并且会呈现未定义的行为。
例如,这个完全安全(我猜)的自引用示例需要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 处,并且前几千字节永远不会映射,以便运行时可以捕获 NULL 取消引用。由于最大对齐要求通常只有几个字节,因此这永远不会指向有效内存。