C 抱怨将 char** 值传递给函数,但C++没有
C complains about passing char** value to function taking char const*const*const but C++ doesn't
在解释和创建函数参数的类型时,我很难理解为什么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++中,以下初始化都是有效的 同时,在确定函数参数的局部变量类型时,C和C++都同样考虑了顶级cv限定符。例如,在C和C++中,以下代码都是格式错误的 在C和C++中,在其参数上声明了一个顶级cv限定的函数,以后可以用其参数的完全不同的cv限定来定义。顶级cv资格的任何差异都不构成C++中的函数重载。 此外,您反复提到, 同时,由于与顶级cv限定符无关的原因,您编辑的代码示例无法编译。由于C语言中没有从 这确实涉及到C和C++之间的区别。在C和C++中,都不允许从void foo(const int i);
void bar(int i);
void (*pfoo)(int) = foo; // OK
void (*pbar)(const int) = bar; // OK
void foo(const int i) {
i = 5; // ERROR!
}
char *[]
被解释为char **
是相关的。我看不出有什么关联。在函数参数列表中,T []
声明总是等价于T *
声明。但这与顶级简历预选赛完全无关。char **
到const char *const *
的隐式转换,因此无法编译。请注意,此转换根本不涉及也不关心任何顶级cv限定符。影响此转换的const
限定符仅存在于第一和第二间接级别上。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
的代码时,必须遵守a
和b
上的这些限定符,因此不能忽略这些限定符。
但是,请考虑foo
的调用方的视图。foo
将a
视为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 * 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-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
- 为字符串中每 N 个字符插入空格的函数没有按照我认为的方式工作?
- 为什么返回类型的'const'限定符对标有 __forceinline/内联的函数没有影响?
- 为什么我的 while 函数没有终止?
- C++小函数没有内联
- 为什么我的突变器函数没有设置任何内容?还是我的构造函数?
- 为什么 class 的函数没有在 main 中被调用?
- 将参数从一个函数传递到另一个函数(没有模板) - C++
- 为什么此构造函数没有给出不完整的类型错误?
- 类构造函数 - 没有构造函数的实例
- 简单的计数和求和函数没有按照我预期的方式工作
- C++ 构造函数没有匹配函数
- 这个成员函数没有被调用,我不知道如何纠正它
- 我的函数没有返回值
- 为什么这些静态函数没有按预期从另一个线程返回?
- 为什么我的自定义"::swap"函数没有被调用?
- 尝试使用递归和指针到指针反转链表,但反转函数没有给出预期的正确输出
- 抛出的构造函数没有泄漏
- 尽管传递了一个右值,为什么我的移动构造函数没有被调用?
- 为什么 std::find_if 和相关函数没有整个容器的重载?
- 调用没有匹配函数 &没有命名的成员