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

时间:2013-12-03 18:35:26

标签: c++ c const volatile qualifiers

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

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

例如,当传递给函数时,argv的{​​{1}}变为char* []并且我真的不明白为什么,我期望和“想要”是{{ 1}}但我得到了这种行为。

你也可以阅读this article in PDF来讨论C和C ++之间的这种差异,文章也以这句话结尾:

  

虽然C ++忽略了参数中的顶级cv限定符   在确定函数签名时的声明,它没有   完全忽略那些cv-qualifiers。

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

有人可以解释为什么这种行为与C ++一样?

修改

我的一个例子是

char**

如果您使用char * const *进行编译,则会收到预期的错误

#include <stdio.h>

void foo(int argc, const char *const *const argv) {
  printf("%d %s\n", 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) { ^

的事实

5 个答案:

答案 0 :(得分:6)

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

对于by-value参数,顶级限定符仅影响副本,并且完全独立于用于复制构造该参数的原始值。如果未从签名中删除顶级限定符,则以下两个函数将是有效且不同的重载:

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

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

答案 1 :(得分:5)

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

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

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

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

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

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

实际上它在C语言中是完全合法的.C只需要在同一范围内的同一实体的所有声明都使用兼容的类型(6.7 / 4)。上面的两个声明是 compatible ,这意味着它们只是合法地重新声明相同的功能。 (在C ++中,上述声明也是合法的,它们也重新声明了相同的功能。)

对于其他示例,在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-qualifiers完全无关。

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

这确实涉及C和C ++之间的差异。在C和C ++中,不允许从char **const char **的隐式转换(例如,请参阅here)。但是,C ++允许从char **const char *const *的隐式转换,而C仍然没有。您可以阅读更多相关信息here。但请注意,在所有这些情况下,顶级cv限定符完全无关紧要。他们根本没有任何作用。

答案 2 :(得分:4)

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

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

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

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

但是,请将来电者的视图考虑为foofooa视为常量或将b视为易失性的事实与调用者无关。它指定的任何参数都被复制(例如,寄存器或堆栈)以传递给foo。它所做的就是传递价值观。如果foo的声明没有限定词:

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

然后调用者的行为不会改变:无论哪种方式,它只是传递参数的值并调用foo。因此,foo的这两个声明与调用者的视图相同,因此它们具有相同的签名。

答案 3 :(得分:3)

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

将此示例编译为C的原因产生警告,但C ++不会产生任何诊断,因为C和C ++将char *[]视为不同的类型,或者因为它们丢弃或插入{{1}在不同的地方,但仅仅因为C和C ++以不同的方式定义“兼容的指针类型”; C ++放松了规则,因为C的严格规则并没有阻止真正的错误。

请考虑一下:constchar const * const * const不合法的char **究竟能做些什么?由于无法进行任何修改,因此无法引入任何错误,因此这种限制几乎没有价值。

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

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

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

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

C ++仔细定义了不兼容的指针类型,这样就不允许使用上述类型,同时仍然放宽C中的规则,以便允许比C更安全的程序。

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

  

要使两个指针类型兼容,两者都应具有相同的资格,并且两者都必须   是兼容类型的指针。

  

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

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


  

我想知道这句话可能意味着什么。

他最有可能提到的事实是,如果在定义函数时将参数声明为const,则编译器将不允许函数定义对参数执行非const操作。

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.
}

答案 4 :(得分:1)

观察结果太大而无法发表评论。

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

恕我直言:从调用函数的观点来看,3 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