新标签:胡说八道 = 有 99% 的可能性有错 = 今天工科猪喵喵又和群友学了什么
今天前几天跟 @Sharzy 和 @Lancern 学了 C 语言的指针转换什么时候是安全的。具体而言,X *
可以 By-value 转换为 Y *
(其实就是一个 X 类型的 lvalue 可以当成一个 Y 类型的 lvalue),当且仅当:
存在一个类型 T(自身可能是指针),自然数 N(可能是 0):
X = T (N 个 * 或者 *const)
Y = T' (N 个 *const)
其中
T' = T or const T
因此以下转换是安全的:
T * -> T *
char * -> const char *
char ** -> char *const * -> const char *const *
以下转换是不安全的:
const char * -> char *
// 允许修改 const char
char ** -> const char **
// 允许将一个 const char * transmute 到 char *,然后和上面那个一样允许修改 const char
其中最后一个例子是去找群友学习的动机。如果只把例子列出来看上去很像函数的负类型上的 contravariance,但是由于指针既能读又能写,事实上更接近 Invariant(写的那一半是 Contravariant 的,读的那一半是 Covariant 的)。作为工科猪喵喵对上述规则的理解方式如下:
从想允许的行为考虑,以 X -> Y
表示 X 的 lvalue 可以当成 Y 的 lvalue 用,这个关系可以以以下方式生成:
T type
--------------------
T -> T, T -> const T
X, Y type, X -> Y
--------------------
X * -> Y *const
X, Y type, X -> Y
--------------------
X *const -> Y *const
这几条规则分别来自于:
- 类型不变或者加个 const 是安全的
- 如果 X 类型的 lvalue 可以被安全地当成 Y 类型的 lvalue 用,那么 X 类型的指针变成 Y 类型的 const 指针,后者只允许读,一定是安全的。
- 和 2 类似,但是 X 的 lvalue 也只读,这也是安全的。由于转换后是 const 指针,因此这个 lvalue 也不会被覆写,因此 const 保证也被保留。
可以注意到上面这几条规则生成的和本文开头的规则一致。
从想禁止的行为考虑,剥掉最外面一层指针考虑 lvalue,希望避免把一个不可写的 lvalue 当成一个可写的 lvalue。这件事情会发生当且仅当在剩下的可能很多层的指针中,有相邻的几层形如(以下也是讨论 lvalue 的类型,*const?
表示 *const
或者 *
):
const A -> B
(const 直接丢失)A * -> const B *
(允许将const B *
写到一个A *
lvalue 里,一次解引用后让const B
变成了A
)A *const *
->const B *const *
(允许将const B *const *
写到一个A *const *
的 lvalue 里,两次解引用后让const B
变成了A
)A *const *const * -> const B *const *const *
(同上,三次解引用)- …
给定原始类型,这些禁止规则的补也正好生成了本文开头的目标类型集合:
考虑转换前后类型 const-qualifier 不同的最内层,假设是第 层。那么 T
正好就是这最内 层。对于任意 可以归纳证明被转换到的类型中第 k
层一定带 const。
- 如果第 k 层 const-qualifier 不同,那么根据规则 0,一定是转换前 non-const,转换后 const
- 如果 const-qualifier 相同,考虑
A = { j < k | 第 j 层转换前后不同 }
。A 非空,取 A 最大值k'
,归纳假设说明 内每一层转换后都是 const。然后使用规则 (k - k’)。