新标签:胡说八道 = 有 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

这几条规则分别来自于:

  1. 类型不变或者加个 const 是安全的
  2. 如果 X 类型的 lvalue 可以被安全地当成 Y 类型的 lvalue 用,那么 X 类型的指针变成 Y 类型的 const 指针,后者只允许读,一定是安全的。
  3. 和 2 类似,但是 X 的 lvalue 也只读,这也是安全的。由于转换后是 const 指针,因此这个 lvalue 也不会被覆写,因此 const 保证也被保留。

可以注意到上面这几条规则生成的和本文开头的规则一致。


从想禁止的行为考虑,剥掉最外面一层指针考虑 lvalue,希望避免把一个不可写的 lvalue 当成一个可写的 lvalue。这件事情会发生当且仅当在剩下的可能很多层的指针中,有相邻的几层形如(以下也是讨论 lvalue 的类型,*const? 表示 *const 或者 *):

  1. const A -> B (const 直接丢失)
  2. A * -> const B * (允许将 const B * 写到一个 A * lvalue 里,一次解引用后让 const B 变成了 A
  3. A *const * -> const B *const * (允许将 const B *const * 写到一个 A *const * 的 lvalue 里,两次解引用后让 const B 变成了 A
  4. A *const *const * -> const B *const *const * (同上,三次解引用)

给定原始类型,这些禁止规则的补也正好生成了本文开头的目标类型集合:

考虑转换前后类型 const-qualifier 不同的最内层,假设是第 k0k_0 层。那么 T 正好就是这最内 k0k_0 层。对于任意 k>k0k > k_0 可以归纳证明被转换到的类型中第 k 层一定带 const。

  • 如果第 k 层 const-qualifier 不同,那么根据规则 0,一定是转换前 non-const,转换后 const
  • 如果 const-qualifier 相同,考虑 A = { j < k | 第 j 层转换前后不同 }。A 非空,取 A 最大值 k',归纳假设说明 j(k,k)j \in (k', k) 内每一层转换后都是 const。然后使用规则 (k - k’)。