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

2022-01-23 00:00:00 gcc c constants clang c++

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

I know implicit conversion from char ** to const char ** cannot be done and why, and that the conversion to char *const * works. See bottom for links to explanation on that.

除了一件特定的事情之外,这一切都是有意义的.所以我有以下代码:

It all makes sense apart from one particular thing. So I have the following code:

#include <stdio.h>

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

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

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

If I compile this as a C++ code, it compiles quite fine. However, if the same code is compiled as a C code only, I get an error (well, a warning, but let's suppose -Werror, i.e. treat warnings as errors).

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 尝试了各种标准.

Both behaviours are standard-independent and also compiler-independent. I tried various standards with both gcc and clang.

这个查询有两个原因.首先,我想了解是否有区别,其次,我有一个函数对指针的任何层都不做任何事情,我需要它能够与 const char ** 一起工作以及 char *const *char **.显式转换每个调用是不可维护的.而且我不知道函数原型应该是什么样子.

There are two reasons for this inquiry. Firstly, I want to understand whether there is a difference and, secondly, I have a function that does nothing with any layer of the pointers and I need it to be able to work with const char ** as well as char *const * and char **. Explicitly casting each call is not maintainable. And I have no idea how should the function prototype look like.

这是引发我好奇心的问题:从 char** 到 const char** 的隐式转换

This is the question that started my curiosity: Implicit conversion from char** to const char**

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

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

If the links are confusing related to this question, feel free to edit them out.

推荐答案

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

C and C++ are different in this respect. I don't have an answer to why C++ is more generous, other than that the C++ behaviour seems to me to be correct.

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

C simply doesn't allow indirect const conversion. That is a conservative, easy-to-implement restriction, with the unfortunate consequence that you cannot provide char*[] to a function expecting char const* const*. The restriction is in §6.3.2.3, paragraph 2, and it is simply not recursive:

对于任何限定符 q,指向非 q 限定类型的指针可以转换为指向 q 限定类型的指针类型的版本;存储在原始指针和转换指针中的值应该比较相等.

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 段中的复杂公式进行转换.允许转换

C++ allows conversions according to a somewhat complex formulation in §4.4 [conv.qual], paragraph 3. It is permitted to convert

T cvn Pn-1cvn-1 … P1cv1 P0cv0?T cv'n Pn-1cv'n-1 … P1cv'1 P0cv'0

T cvn Pn-1cvn-1 … P1cv1 P0cv0 ? T cv'n Pn-1cv'n-1 … P1cv'1 P0cv'0

(其中 T 是一个类型;P1…Pn 是指针/数组类型构造函数,并且每个 cv0…cvn 可能是 constvolatile)

(where T is a type; P1…Pn are pointer/array type constructors, and each cv0…cvn is some possibly empty subset of const and volatile)

前提是:

  1. 对于每个 k >0, cvkcv'k 的子集(所以你不能删除constvolatile),以及

如果 cvkcv'k 对于某些 k >0,后面的所有cv'i>k都包括const.

在实际标准中,该表达式是相反的;我把它放在声明的顺序中,而在标准中它是按照指针/数组构造函数的应用顺序.不过,我没有改变编号的方向,这就是为什么它们从右到左编号的原因.我还遗漏了一些细节――例如,两个 T 并非严格必须相同――但我认为它给出了意图的概念.

In the actual standard, that expression is reversed; I put it in the order of declaration, whereas in the standard it is in order of application of the pointer/array constructors. I didn't change the direction of the numbering, though, which is why they are numbered right to left. I also left out some details -- for example, it's not strictly necessary for the two Ts to be identical -- but I think it gives an idea of the intention.

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

The explanation for the first restriction is reasonably obvious. The second restriction prevents the problem described in the C FAQ, where a const pointer might be stored into a non-const pointer object, and then subsequently used to mutate the const object it points to.

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

The bottom line is that in C++, your prototype const char *const * param will work with arguments of type char**, const char**, or even char*const*, but in C only the last one will work without warning, and it is the least useful. The only workaround I know of (other than switching to C++) is to ignore the warning.

关于它的价值,在 Rationale 部分中有一条注释exec* 接口 的 Posix 规范关于这对这些原型造成的问题,以及 Posix 选择的解决方法,即使用 char*[]作为原型并在文字上注意这些是恒定的:(添加了重点)

For what it's worth, there is a note in the Rationale section of the Posix specification of the exec* interfaces about the problem this causes for these prototypes, and the workaround selected by Posix, which is to use char*[] as the prototype and textually note that these are constant: (emphasis added)

包含关于 argv[]envp[] 是常量的声明是为了向未来的语言绑定编写者明确说明这些对象是完全常量.由于 ISO C 标准的限制,不可能在标准 C 中陈述这个想法. 为 argv[ 指定两个级别的 const-qualificationexec 函数的 ]envp[] 参数似乎是自然的选择,因为这些函数不会修改指针数组或函数指向的字符,但这将不允许现有的正确代码.相反,只有指针数组被标记为常量.

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.

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

There's a useful compatibility chart following that paragraph, which I didn't quote because of the formatting limitations of this site.

相关文章