3

I'm having difficulties in understanding why C++ behaves in a more "relaxed" way than C when it comes to interpreting and creating types for the parameters of a function.

C does the simplest thing in the world, it sticks with what you write and that's it, C++ on the other hand operates in a twisted way that I can't really comprehend.

for example the popular argv which is a char* [] when passed to a function becomes char** and I really don't get why, what I expect and "want" is char * const * but I got this behaviour instead.

You can also read this article in PDF that talks about this differences between C and C++, the article also ends with this phrase:

Although C++ ignores top-level cv-qualifiers in parameter declarations when determining function signatures, it does not ignore those cv-qualifiers entirely.

and since I can't find this issue online ( Embedded System Programming - February 2000 , and this old issues are free ), I'm wondering what this phrase could possibly mean.

Someone can explain why this behaviour is the way it is in C++ ?

EDIT:

One of my examples is

#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);
}

and if you compile this with gcc 4.8.1 you get the expected error

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) {
      ^

this output makes implicit the fact that argv is interpreted as char**

4

5 回答 5

6

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

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

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

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

于 2013-12-03T19:16:06.200 回答
5

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

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

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

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

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

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

实际上,它在 C 中是完全合法的。C 只要求同一范围内同一实体的所有声明都使用兼容类型(6.7/4)。上面的两个声明是兼容的,这意味着它们只是合法地重新声明了相同的函数。(在 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-qualifiers 无关的原因,您编辑中的代码示例也无法编译。它无法编译,因为在 C 语言中char **没有隐式转换。const char *const *请注意,此转换不涉及也不关心任何顶级 cv 限定符。影响此转换的const限定符仅存在于第一层和第二层间接层上。

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

于 2013-12-03T19:53:10.620 回答
4

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

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

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

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

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

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

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

于 2013-12-03T19:16:02.990 回答
3
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++ 放宽了规则,因为 C 的严格规则并不能防止真正的错误。

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

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

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.
}
于 2013-12-03T19:25:20.387 回答
1

意见太大,无法评论。

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

const恕我直言:从调用函数的角度来看,这两种语言在 3 日都没有区别。

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

于 2013-12-04T18:44:34.833 回答