先说些题外话,今天学习execve(2)的使用,由于书上代码使用的是C89标准,所以下面这种代码都被我修改了
char* s[] = { "aaa", "bbb", "cc" };
也就是在char前面加个const,因为"aaa"、"bbb"、"cc"都是字符串字面值(string literal),在C++标准中string literal只能转换成const char*,原因是即使用char*指向string literal,也是无法修改的。比如上述代码不做修改在旧标准中是可行的,但是妄图用s[0][0] = 'd'来使s[0]变成"daa",那么运行时会报错,因为string literal是存在静态常量区的,不可修改,但是可以取得string literal的地址(用指针类型表示)。这就跟char*的语义产生了冲突,因为char*指向的是char而不是const char,理论上是可以赋值的。
于是当我改成const char* s[]后,传入execve(2)时编译报错:期待参数类型是char * const*,但是传入参数类型是const char **。
int execve(const char *filename, char *const argv[], char *const envp[]);
当我去掉const(也就是变回了char* s[])后,编译通过。从例子可以看出,char**可以转换成char* const*,但是const char**不能显式转换成char* const*,这样的规则除了像我这样实际修改代码测试,还有什么办法记下来呢?
这个问题博客最后再回答,先出道题:以下类型的变量p,分别具有什么性质?
具体而言,p、p[0]、p[0][0]……是否可以修改(即而出p = q、p[0] = q这样的代码),像p[1]、p[2]……和p[0]类型是相同的,性质也相同,
(1)const char* p (2)char const* p (3)char* const p (4)const char** p (5)char const** (6)char* const* (7)char** const
思路简单来说就是“就近原则”+“符号*的消去”+“const性质”,具体看我下面解释
(1)const char* p:离p最近的是*而不是const,因此p可以修改;现在考虑p[0],那么,对一个指针p使用运算符*,操作步骤是消去*到p之间的所有部分,那么*p就变成了const char类型,被const修饰,因此不可以修改
验证代码
char s1[] = { 'a', 'b', 'c' }; char s2[] = { 'a', 'b', 'c' }; const char* p = s1; p[0] = 'd'; // 编译报错 p = s2; // 编译通过
(2)char const* p:离p最近的是*而不是const,因此p可以修改;考虑p[0],消去*后为char const,被const修饰,因此不可以修改。结果和(1)一样
(3)char* const p:离p最近的是const而不是*,因此p不可以修改;考虑p[0],消去* const部分剩下的是char,可以修改。
验证代码:把(1)的const char* p = s1;改成char* const p = s1;
(4)const char** p :离p最近的是*而不是const,因此p可以修改;考虑p[0],消去*后剩下const char*,距离最近的是*而不是const,因此p[0]可以修改;考虑p[0][0],消去*后剩下const char,不可修改;
验证代码
char s1[] = { 'a', 'b', 'c', '