C 抱怨将 char** 值传递给函数,但C++没有

C complains about passing char** value to function taking char const*const*const but C++ doesn't

本文关键字:函数 没有 C++ 值传 char      更新时间:2023-10-16

在解释和创建函数参数的类型时,我很难理解为什么C++的行为比C更"轻松"。

C做了世界上最简单的事情,它坚持你写的东西,就这样,另一方面,C++以一种我无法真正理解的扭曲方式运行。

例如,流行的argv,当传递给函数时是char* [],变成了char**,我真的不明白为什么,我期望和"想要"的是char * const *,但我得到了这种行为。

你也可以阅读PDF中的这篇文章,它谈到了C和C++之间的差异,文章的结尾也是这样的:

尽管C++忽略了参数中的顶级cv限定符在确定函数签名时声明,它不完全忽略那些简历限定符。

由于我在网上找不到这个问题(嵌入式系统编程-2000年2月,这些旧问题是免费的),我想知道这个短语可能意味着什么。

有人可以解释为什么C++中会出现这种行为?

编辑:

我的一个例子是

#include <stdio.h>
void foo(int argc, const char *const *const argv) {
  printf("%d %sn", argc, argv[0]);
}
int main(int argc, char *argv[]) {
  foo(argc, argv);
  return (0);
}

如果你用gcc 4.8.1编译它,你会得到预期的错误

gcc cv_1.c 
cv_1.c: In function ‘main’:
cv_1.c:8:3: warning: passing argument 2 of ‘foo’ from incompatible pointer type [enabled by default]
   foo(argc, argv);
   ^
cv_1.c:3:6: note: expected ‘const char * const* const’ but argument is of type ‘char **’
 void foo(int argc, const char *const *const argv) {
      ^

此输出隐含了argv被解释为char** 的事实

函数参数可以通过值或引用传递。在通过引用的情况下,没有顶级限定符,所以我们可以忽略这种情况。

在按值参数的情况下,顶级限定符只影响副本,并且完全独立于用于复制构造该参数的原始限定符。如果没有从签名中删除顶级限定符,那么以下两个函数将是有效的,并且重载不同:

void f(int       i);
void f(int const i);

现在的问题是,如果调用f(1),应该选择两个重载中的哪一个?这里的问题是,参数是否为const并不影响它的构造依据,因此编译器永远无法解决哪个是正确的重载。解决方案很简单:在签名中,顶级限定符被删除,并且两者都是相同的函数。

您链接的PDF文章包含许多关于C和C++在处理顶级cv限定符时的差异的错误陈述。这些差异要么不存在,要么与文章中所暗示的性质不同。

事实上,在确定函数签名和函数类型时,C和C++都有效地忽略了函数参数声明中的顶级cv限定符。C和C++语言标准中的措辞(以及底层机制)在概念上可能不同,但最终结果在两种语言中都是相同的。

C++在确定函数类型时确实直接忽略了参数上的顶级cv限定符,如8.3.5/5:所述。"生成参数类型列表后,在形成函数类型时,修改参数类型的任何顶级cv限制符都将被删除。"

C没有直接忽略这些限定符,而是依赖于兼容类型的C特定概念。它说,仅在参数的顶级cv限定符上不同的函数类型是兼容的,这意味着它们在所有方式和目的上都是相同的。6.7.5.3/15中函数类型兼容性的定义称:"在确定类型兼容性和复合类型时,[…]用限定类型声明的每个参数都被视为具有其声明类型的非限定版本。"

链接的PDF文章指出,在C中,以下声明序列是非法的

void foo(int i);
void foo(const int i);

事实上,它在C.C中是完全合法的。C只是要求同一范围内同一实体的所有声明都使用兼容的类型(6.7/4)。上面两个声明都是兼容性的

对于其他示例,在C和C++中,以下初始化都是有效的

void foo(const int i);
void bar(int i);
void (*pfoo)(int) = foo;       // OK
void (*pbar)(const int) = bar; // OK

同时,在确定函数参数的局部变量类型时,C和C++都同样考虑了顶级cv限定符。例如,在C和C++中,以下代码都是格式错误的

void foo(const int i) {
  i = 5; // ERROR!
}

在C和C++中,在其参数上声明了一个顶级cv限定的函数,以后可以用其参数的完全不同的cv限定来定义。顶级cv资格的任何差异都不构成C++中的函数重载。


此外,您反复提到,char *[]被解释为char **是相关的。我看不出有什么关联。在函数参数列表中,T []声明总是等价于T *声明。但这与顶级简历预选赛完全无关。

同时,由于与顶级cv限定符无关的原因,您编辑的代码示例无法编译。由于C语言中没有从char **const char *const *的隐式转换,因此无法编译。请注意,此转换根本不涉及也不关心任何顶级cv限定符。影响此转换的const限定符仅存在于第一和第二间接级别上。

这确实涉及到C和C++之间的区别。在C和C++中,都不允许从char **const char **的隐式转换(例如,请参阅此处)。然而,C++允许从char **const char *const *的隐式转换,而C仍然不允许。你可以在这里阅读更多关于它的信息。但请再次注意,在所有这些情况下,顶级简历限定符是完全无关的。他们根本不起任何作用。

这只是猜测,但原因是带有限定符的函数参数只是参数的副本。考虑:

void foo(int * const a, volatile int b) { … }

这些限定符表明,函数定义中的代码不会修改a(因为它是常量),并且b的值可能以C++实现未知的方式访问。(这很奇怪;volatile对象通常是硬件寄存器,或者可能是进程之间共享的数据。但假设我们正在调试一个问题,所以我们暂时将b标记为volatile,以确保我们可以在调试器中访问它。)

C++实现在编译和执行定义foo的代码时,必须遵守ab上的这些限定符,因此不能忽略这些限定符。

但是,请考虑foo的调用方的视图。fooa视为const或将b视为volatile这一事实与调用者无关。它指定的任何参数都被复制(例如,复制到寄存器或堆栈)以传递给foo。它所做的只是传递值。如果foo的声明没有限定符:

void foo(int *a, int b) { … }

那么调用者的行为不会改变:无论哪种方式,它都只是传递参数的值并调用foo。因此,从调用者的角度来看,foo的这两个声明是相同的,因此它们具有相同的签名。

void foo( char const * const * const) {}
void bar( char *x[]) {
  foo(x); // warning in C, nothing in C++
}

将此示例编译为C会产生警告,而C++不会产生任何诊断,这并不是因为C和C++将char *[]视为不同的类型,也不是因为它们在不同的位置丢弃或插入const,而是因为C和++对"兼容指针类型"的定义不同;C++放宽了规则,因为C的严格规则并不能防止真正的错误。

考虑一下:如果char const * const * constchar **不合法,你到底能用它做什么?由于无法进行任何修改,因此不可能引入任何错误,因此这样的限制没有什么价值。

然而,这并不是说插入const不允许可能产生错误的代码。例如:

void foo(char const **c) { *c = "hello"; }
void bar(char **c) {
  foo(c);
  **c = 'J';
}

如果允许,上面的代码将写入字符串常量,这是非法的。

C++小心地定义了不兼容的指针类型,这样就不允许出现上述情况,同时仍然放宽了C的规则,以允许比C更安全的程序。

C规则的一个优点是它们非常简单。基本上:

对于兼容的两种指针类型,两者应具有相同的资格是指向兼容类型的指针。

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

另一方面,C++的规则适用于几个段落,并使用复杂的定义来指定允许的指针转换。兔子洞从C++11 4.4[conv.qual]第4段开始。


我想知道这个短语可能是什么意思。

他很可能指的是这样一个事实,即如果在定义函数时将参数声明为常量,那么编译器将不允许函数定义对该参数执行非常量操作。

void foo(int x);
void bar(int x);
void foo(int const x) {
  ++x; // error, parameter is const
}
void bar(int x) {
  ++x; // okay, parameter is modifiable.
}

观测值太大,无法发表评论。

只有char const * const * const x中的第一个const引发C警告
C++(Visual)抱怨8个中的2个。不知道为什么?

IMHO:从调用函数的角度来看,这两种语言在第三个const上都显得多余。

void fooccc( char const * const * const x) { if(x) return; }
void foocc_( char const * const *       x) { if(x) return; }
void fooc_c( char const *       * const x) { if(x) return; }
void fooc__( char const *       *       x) { if(x) return; }
void foo_cc( char       * const * const x) { if(x) return; }
void foo_c_( char       * const *       x) { if(x) return; }
void foo__c( char       *       * const x) { if(x) return; }
void foo___( char       *       *       x) { if(x) return; }
int g(char *x[]) {
  fooccc(x); // warning in C passing argument 1 of 'fooccc' from incompatible pointer type
  foocc_(x); // warning in C "
  fooc_c(x); // warning in C "   error in C++ cannot convert parameter 1 from 'char *[]' to 'const char **const ' Conversion loses qualifiers
  fooc__(x); // warning in C "   error in C++ cannot convert parameter 1 from 'char *[]' to 'const char **'       Conversion loses qualifiers
  foo_cc(x); // no problem in C  no problem in C++
  foo_c_(x); // no problem in C  no problem in C++
  foo__c(x); // no problem in C  no problem in C++
  foo___(x); // no problem in C  no problem in C++
  return 0;
  }

注意:Eclipse,gcc-std=c99-O0-g3-Wall
C++Visual Studio 10.0