ANSI C 标准中关于指针赋值的约束条件

先看一段包含警告的代码:

1
2
3
4
5
6
void foo(const char **p) { } 

int main(int argc, char **argv) {
foo(argv);
}
// warning: argument is incompatible with prototype

我们可能认为,由于实参 char *s 与形参 const char *p 应该是相容的(标准库中所有的字符串处理函数都是这样写的),那么,为什么实参 char **argv 与形参 const char **p 却不能相容呢?

容易混淆的 const

回答上述问题前先看一下关键字 const 的用法和实际使用的含义。注意,它不能把变量变成常量!在一个符号前加上 const 限定符只是表示这个符号不能被赋值

这意味着,我们可以取一个 const 变量的地址,并且可以改变它的值(但最好还是不要这样)。回首往事,const 关键字原先如果命名为 readonly 就好多了,因为 readonly 没有不能改变的意思(外部可以去改变),而 const 只表示自身不会变。const 的最简单用法:

1
const int num1 = 10;

当在赋值号两边加上指针,就要注意了:

1
2
3
const int *num2 = &num1;
int i = 27;
num2 = &i;

const 修饰符修饰的不是 int 指针,而是 int 本身,意味着 int 不能被改变,而 int 指针的值(地址)可以被改写。const 最有用的地方就是用来修饰函数的形参,这样表示该函数不会去修改实参指针所指向的数据内容。它声称「我给你一个指向它的指针,但你不能修改它」。这个约定类似于 void * 的用法。尽管它可以用于任何类型,但通常被限定于把指针从一种类型转换为另一种类型。

总结:

  • int const *p(int 只读)
  • const int *p(int 只读)
  • int * const p(指针只读)
  • int const * const p(都只读)

指针赋值的约束条件

回到问题本身,ANSI C 标准中有这么一句话(约束条件):

每个实参都应该具有自己的类型,这样它的值就可以赋值给它所对应的形参类型的对象(该对象的类型不能含有限定符)。

这就是说参数传递过程类似于赋值。所以,这样的代码也是非法的:

1
2
3
char **cp;
const char **ccp;
ccp = cp;

要使指针的赋值合法,必须满足下列约束条件:

  1. 两个操作数都是指向有限定符或无限定符的相容类型的指针。
  2. 左边指针所指向的类型必须具有右边指针所指向类型的全部限定符(「无限定符」也被看作是一种限定符)。

先来看一下为什么实参 char *const char * 匹配,即:

1
2
3
char *cp;
const char *ccp;
ccp = cp;

它之所以合法,是因为它满足:

  • 左操作数是一个指向有 const 限定符修饰的 char 的指针。
  • 右操作数是一个指向没有限定符修饰的 char 的指针。(相当于空集可以包含于其它集合)
  • char 类型与 char 类型是相容的,左操作数所指向的类型具有右操作数所指向的类型的限定符(无 + const)。

注意,反过来就不能赋值了,比如:

1
cp = ccp; // 产生警告

类似地,const char ** 是一个指向没有限定符修饰的指针类型。注意两个指针本身 const char **char ** 都没有被 const 限定符修饰。然而,它们指向的类型不一样,const char ** 指向 **const char ***,char ** 指向 **char ***。

  • const charchar → 相容
  • const char *char * → 不相容(注意注意!有点奇葩!)
  • 我猜 char * constchar * 是相容的。

相容性是不能传递的。

换言之,

  • 左操作数的类型是 FOO_2,它是一个指向 FOO 的指针,而 FOO 是一个没有限定符的指针,它指向一个带有 const 限定符的 char 类型。
  • 右操作符的类型是 BAZ_2,它是一个指向 BAZ 的指针,而 BAZ 是一个没有限定符的指针,它指向一个没有限定符的字符类型。

FOOBAZ 所指向的类型是相容的,而且它们本身都没有限定符,所以两者之间可以赋值(注意只是单向的)。但是 FOOBAZ 所指向的类型相容并不意味着 FOO_2BAZ_2 所指向的类型也相容,所以即使 FOO_2BAZ_2 都没有限定符,但它们之间不能进行赋值(双向都不行,因为所指向的对象本身不相容)。

参考

《C 专家编程》(Expert C Programming)p19-21