2013-12-03 214 views
3

我很难理解为什么C++的行为比C更加“放松”,当涉及到解释和为函数的参数创建类型。C抱怨通过char **值传递给char const * const * const函数,但C++不会

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

例如流行argv传递给函数时,这是一个char* []变得char**,我真的不知道为什么,我的期望和“希望”是char * const *,但我得到这个行为来代替。

您还可以阅读this article in PDF谈到有关C和C之间的这种差异++,文章还用这句话结束:

虽然C++确定功能签名时忽略顶级CV-预选赛参数 声明,它不会 完全忽略那些cv-qualifiers。

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

有人可以解释为什么这种行为是在C++中的方式?

编辑:

我的一个例子是

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

这种输出使得隐含的事实, argv被解释为char**

+7

你的argv的例子实际上是C++继承C.你的PDF会谈约*签名的东西*,它们仅用于重载分辨率。与简历检查没有任何关系。 –

+0

@HansPassant对于我所关心的,如果这个机制存在于顶层,它仍然会抛弃我的'const',并且我想知道这个怪异行为背后的原因是什么。在这个例子中'argv'只是一个玩具。 – user2485710

+3

请用一个具体的例子来幽默我们。 –

回答

6

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

对于按值参数,顶级限定符只影响副本,并且完全独立于用于复制构建该参数的原始数据。如果顶级预选赛并没有从签名下降,以下两种功能将是有效的和不同的重载:

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

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

+0

我不明白这一点,对我来说关键的一步是,一个命名变量可能会有一个cv-qualifier,从你所说的话看起来它只是为了节省创建问题的临时值,看起来像权衡在那儿;但是你可以参考C++标准中的某个特定段落吗? – user2485710

+0

@ user2485710:不,问题不是临时性的,而是重载解决方案。如果你想要,用上面的命名变量替换1。顶级限定符适用于该函数的参数。对象是const或不是const,它们的构造函数(或转换函数)的参数集完全相同。顶级'const'与调用方没有关系,编译器不能使用它来区分一个调用与另一个调用。选择一个函数,添加/删除顶级限定符,考虑哪些调用对任一签名都有效:相同。 –

4

这是一个猜测,但原因是具有限定符的函数参数只是参数的一个副本。试想一下:

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

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

C++实现必须履行上ab当它被编译和执行定义foo代码这些限定符,所以它不能忽视这些限定符。

但是,请将调用者视为foofooa视为const或b作为易失性与调用方无关。无论它指定的参数是否被复制(例如,,寄存器或堆栈)传递给foo。它所做的只是传递价值观。如果foo声明没有限定词:

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

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

+0

所以,简而言之,你是说可观察到的行为没有变化(因为'foo'),这足以重新考虑这个问题的严重性,并且可能会说它的方式足够好吗? – user2485710

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严格的规则并不能防止真正的错误。

请考虑:如何处理char const * const * constchar **不合法的问题?由于不能进行任何修改,因此不可能引入任何错误,因此这种限制几乎没有价值。

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

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,那么编译器将不允许函数定义对参数执行非常量操作。

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

目前还不清楚为什么编译器会将'argv'解释为'char **'(因为它是'char * []',因此编译器会将它编译为第一篇文章)在那里我开始尝试这个。 – user2485710

+0

@ user2485710 C中的'char * []'和'char **'参数有什么区别吗? – bames53

+0

其中'a'是'int a []',我把'a'看作'int * const a',所以'argv'的'char **'实际上并不是我期望看到的。 – user2485710

5

您链接的PDF文章包含了一些关于他们的待遇顶级CV-预选赛的C和C++之间的差异不正确的语句。这些差异要么不存在,要么与文章中隐含的内容不同。

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

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

C不依赖于直接忽略这样的限定词,而是依赖于兼容类型的特定于C的概念。它说只有在顶级cv-qualifiers参数上有所不同的功能类型是兼容,这对于所有的手段和目的来说意味着它们是相同的。 6.7.5.3/15中函数类型兼容性的定义如下:“在确定类型兼容性和复合类型时,声明合格类型的每个参数都被视为具有不合格的版本其声明的类型“。

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

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

在现实中它是。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-qualification中的任何差异都不构成C++中的函数重载。


此外,您多次提到,char *[]被解释为char **为相关的事情。我没有看到相关性。在函数参数列表中,T []声明总是等效于T *声明。但是这与顶级cv-qualifiers完全没有关系。

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

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

+0

描述此标准的段落是什么?模板也一样吗? – user2485710

+0

我敢打赌。至于文章,这是相当古老的。在2000年前的那些年里,我身边的人仍然很高兴地使用了在DOS环境下工作的IIRC“Turbo C++ 32”编译器,并且在涉及所有重要的部分时遇到了很多问题。也许本文的作者简单地使用了类似的东西.. – quetzalcoatl

1

观察结果太大而不能发表评论。

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

恕我直言:两种语言在第三个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

+0

我相信C++规则归结为:在任何级别插入常量需要在所有更高级别插入const,除了顶级无关紧要。 – bames53

+0

@ bames53如果您可以随时访问另一个C++编译器,请感谢您的发现和编译器信息。 – chux

+0

在[isocpp.org](http://isocpp.org/get-started)网站上列出了几个在线编译器,但是不是广泛地测试实现,我只是阅读C++规范。你的发现在这里与我的阅读和一些快速测试(clang和vC++) – bames53