为什么 C 不允许从 char ** 到 const char *const * 的隐式转换(而 C++ 允许)?

Why C doesn't allow implicit conversion from char ** to const char *const * (and C++ does)?

我知道从 char **const char ** 的隐式转换无法完成以及为什么,并且到 char *const * 的转换有效。请参阅底部的解释链接。

除了一件事之外,这一切都是有道理的。所以我有以下代码:

#include <stdio.h>

void
print(const char *const*param)
{
    printf("%s\n", param[0]);
}

int
main(int argc, char **argv)
{
    print(argv);
    return 0;
}

如果我将它编译为 C++ 代码,它编译得很好。但是,如果相同的代码仅编译为 C 代码,我会得到一个错误(好吧,一个警告,但我们假设 -Werror,即将警告视为错误)。

gcc:

test.c: In function ‘main’:
test.c:12:11: warning: passing argument 1 of ‘print’ from incompatible pointer type [-Wincompatible-pointer-types]
     print(argv);
           ^
test.c:4:1: note: expected ‘const char * const*’ but argument is of type ‘char **’
 print(const char *const*param)
 ^

叮当声:

test.c:12:11: warning: passing 'char **' to parameter of type 'const char *const *' discards qualifiers in nested pointer types [-Wincompatible-pointer-types-discards-qualifiers]
    print(argv);
          ^~~~
test.c:4:25: note: passing argument to parameter 'param' here
print(const char *const*param)
                        ^

这两种行为都与标准无关,也与编译器无关。我尝试了 gccclang 的各种标准。

这个查询有两个原因。首先,我想了解是否存在差异,其次,我有一个函数不对任何指针层执行任何操作,我需要它能够使用 const char ** 以及 char *const *char **。显式转换每个调用是不可维护的。而且我不知道函数原型应该是什么样子。


这个问题引起了我的好奇心: Implicit conversion from char** to const char**

这里是 char ** => const char** 问题的另一个很好的解释: http://c-faq.com/ansi/constmismatch.html

如果与此问题相关的链接令人困惑,请随时将其编辑掉。

C 和 C++ 在这方面是不同的。我不知道为什么 C++ 更慷慨,除了 C++ 的行为在我看来是正确的。

C 根本不允许间接 const 转换。这是一个保守的、易于实现的限制,不幸的是,您不能将 char*[] 提供给期望 char const* const* 的函数。限制在 §6.3.2.3,第 2 段,它根本不是递归的:

For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to the q-qualified version of the type; the values stored in the original and converted pointers shall compare equal.

C++ 允许根据 §4.4 [conv.qual] 第 3 段中稍微复杂的公式进行转换。允许转换

T cv<sub>n</sub> P<sub>n-1</sub>cv<sub>n-1</sub> ... P<sub>1</sub>cv<sub>1</sub> P<sub>0</sub>cv<sub>0</sub> T cv'<sub>n</sub> P<sub>n-1</sub>cv'<sub>n-1</sub> ... P <sub>1</sub>cv'<sub>1</sub> P<sub>0</sub>cv'<sub>0</sub>

(其中T是一种类型;P<sub>1</sub>…P<sub>n</sub> 是 pointer/array 类型构造函数,每个 cv<sub>0</sub>…cv<sub>n</sub> 是一些可能constvolatile)

的空子集

前提是:

  1. 对于每个k > 0cv<sub>k</sub>cv的子集'<sub>k</sub>(所以你不能删除 constvolatile),并且

  2. 如果cv<sub>k</sub>cv'<sub>k</sub> 与某些 k > 0 不同,以下所有 cv'<sub>i>k</sub> 包括 const .

在实际标准中,该表达式是相反的;我把它放在声明的顺序中,而在标准中它是按照 pointer/array 构造函数的应用顺序。不过,我没有改变编号的方向,这就是它们从右到左编号的原因。我还遗漏了一些细节——例如,两个 T 不一定完全相同——但我认为它给出了意图的想法。

第一个限制的解释相当明显。第二个限制防止了 C FAQ 中描述的问题,其中 const 指针可能存储到非 const 指针对象中,然后用于改变它指向的 const 对象到.

最重要的是,在 C++ 中,您的原型 const char *const * param 将使用 char**const char** 甚至 char*const* 类型的参数,但仅在 C 中最后一个将在没有警告的情况下工作,而且它是最没有用的。我知道的唯一解决方法(除了切换到 C++)是忽略警告。

值得一提的是,Rationale section of the Posix specification of the exec* interfaces 中有一条关于这对这些原型造成的问题的注释,以及 Posix 选择的解决方法,即使用 char*[]作为原型并在文字上注明这些是不变的:(强调)

The statement about argv[] and envp[] being constants is included to make explicit to future writers of language bindings that these objects are completely constant. Due to a limitation of the ISO C standard, it is not possible to state that idea in standard C. Specifying two levels of const-qualification for the argv[] and envp[] parameters for the exec functions may seem to be the natural choice, given that these functions do not modify either the array of pointers or the characters to which the function points, but this would disallow existing correct code. Instead, only the array of pointers is noted as constant.

该段后面有一个有用的兼容性图表,由于本网站的格式限制,我没有引用它。