双重标准?为什么只有 char* const&a = "bla" 的警告?

Double standard? Why only a warning for char* const& a = "bla"?

本文关键字:bla 警告 const 标准 为什么 char      更新时间:2023-10-16

在尝试深入研究像这个问题这样的案例背后的机制之后,我仍然不明白为什么下面代码中的第三行只生成警告,而第二行是错误。

int main()
{
const char* const& a = "bla"; // Valid code
const char*& a2 = "bla"; // Invalid code
char* const& a3 = "bla"; // Should be invalid but settles for a warning
return 0;
}

我知道,当引用初始化字符串文字转换为指针引用时,它不应该删除对象具有的任何cv限定符,并且由于转换的类型是const char* const(从字符串文字"bla"转换而来,即const char[4]),所以它似乎与第二行的情况相同。唯一的区别是,被丢弃的const属于C字符串本身,而不是指针。

在GCC 8.2和Clang 6.0.0上复制,不指定任何额外的一致性标志。

gcc的输出:

<source>:4:23: error: cannot bind non-const lvalue reference of type 'const char*&' to an rvalue of type 'const char*'
const char*& a2 = "Some other string literal";
^~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:5:23: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
char* const& a3 = "Yet another string literal";

为什么当前的编译器符合第一种情况而不符合第二种情况?或者,我在这两个案例之间缺少一个根本的区别吗?

字符串文字是数组。"bla"的类型为const char [4]

const char* const& a = "bla";

这是有效的,因为存在从T []T *的转换;在这种情况下,您会得到一个const char *右值。这个右值可以绑定到一个引用,因为它是对const的引用(保持临时值等)。

const char*& a2 = "bla";

无效,因为您试图将临时值绑定到一个非常数引用。

char* const& a3 = "bla";

这是对const的引用,但类型错误(它是指向char的指针,而不是指向const char的指针)。此转换将删除一个const限定符,因此它应该是无效的。一些C++编译器出于向后兼容性的原因允许这样做:在C中,字符串文字具有非常量限定类型(即"bla"将是char [4]),因此将其作为一个硬错误会破坏许多现有代码。

即使在C++中,这也曾经是合法的。在C++11之前,仍然允许为char *(而不是const char *)变量分配字符串文字(但已弃用)。

"双重标准"是因为从不允许将非常量引用绑定到临时引用(C甚至没有引用),所以不存在向后兼容性问题。该标准没有区分"错误"answers"警告";对于任何给定的违反规则的行为,编译是否应该成功由编译器编写者自行决定。

这两种情况都是格式错误的。然而,该标准并不要求编译器拒绝格式错误的程序。因此,接受警告完全符合标准。

或者,我在这两种情况之间缺少的根本区别吗?

主要区别在于,将非常量左值引用绑定到右值从来都不是很好的形式,而将const char*隐式转换为char*在C++11之前一直是很好的格式。向后兼容性是允许后者的一个很好的论据。

让我们使用EAST常量语法来解构它。

const的规则是,它总是适用于它左边的东西,除非它左边什么都没有,在这种情况下,它适用于右边的东西。对于EAST常量,我们总是在右边写常量。

让我们看看代码:

const char* const& a = "bla"; // Valid code

成为

char const * const & a = "bla";

所以char是恒定的,不能改变。

指向字符的指针是常量,也不能更改。

总体而言:这是对一个指针的引用,该指针不能更改为一个字符。

"bla"是一个const C样式数组,它会立即衰减为char const*const。

它之所以是"charconst*const"而不是"charconst*",是因为"bla"的地址是恒定的——字符串"bla)被编译到某个固定位置的执行代码中,当加载到内存中时,它将保持在该内存地址,直到程序终止。

所以现在除了引用之外,我们还有匹配的类型。

T&a=某事;如果某个东西是T类型,并且该东西有地址(它确实有),则它将始终工作。

让我们看看第二个:

const char*& a2 = "bla"; 

EAST常量语法:

char const * & a2 = "bla";

"bla"属于类型

char const * const

这些类型不匹配("bla"的内存位置是固定的)。

也许这个代码会让它更清晰:

char const *stringPtr = "hello";
char const *stringPtr2 = "world";
char const * &stringPtrRef = stringPtr;
std::cout << stringPtr << std::endl;
stringPtrRef = stringPtr2;
std::cout << stringPtr << std::endl;

这将在第一行打印"你好",在第二行打印"世界"。这是因为stringPtr指向的内容发生了变化。

由于"bla"的位置是固定的,我们不能构建一个对它的引用,因为"bla"的引用可以通过将其引用设置为其他内容来更改。这是不可能的。也没有可能的演员阵容,我们可以用它来迫使它成为正确的类型。

这就是为什么即使有警告也无法编译的原因。

让我们看看第三个:

char* const& a3 = "bla";

这已经是EAST常量格式。

使用"char*const&"-生成的引用,虽然不允许更改内存位置,但可以将"bla"修改为"abc"。

也许在某些情况下,你真的想这样做,以节省一些嵌入式系统的内存空间,在这些系统中,"bla"只被用作初始化,再也不会使用了。

这个消息很有道理:

"警告:ISO C++禁止将字符串常量转换为"char*">

因为这基本上与相同

char const *s1 = "bla";
char *s2 = s1;

实际编译时会使用正确的编译器标志(-fpermisive)发出警告。

即使没有-fpermission,我们也可以更改代码以进行强制转换并使其工作。

所以,我理解为什么它可以编译,但我认为这应该是一个错误。ISO C++明确禁止这样做。我的观点是:如果这真的是你想做的,就需要一个演员阵容。