双重标准?为什么只有 char* const&a = "bla" 的警告?
Double standard? Why only a warning for char* const& a = "bla"?
在尝试深入研究像这个问题这样的案例背后的机制之后,我仍然不明白为什么下面代码中的第三行只生成警告,而第二行是错误。
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++明确禁止这样做。我的观点是:如果这真的是你想做的,就需要一个演员阵容。
- 警告处理为错误这里有什么问题
- 使用动态分配的数组会导致代码分析发出虚假的C6386缓冲区溢出警告
- cppcheck在const std::string[]上引发警告
- GCC对可能有效的代码抛出init list生存期警告
- 如何在BST的这个简单递归实现中消除警告
- 关于std::move的使用,是否有编译警告
- g++ 在某个类成员未初始化时不发出警告
- 如何处理来自核心指南检查器的关于gsl::at的静态分析警告
- 使用typeid警告未使用的变量
- 示例C++项目编译中的警告
- 警告:在函数返回类型 [-Wignore 限定符] 时忽略类型限定符
- 如何修复编译器警告 C6386 和 C6385?
- 返回语句后的代码,没有警告
- 获取隐式转换溢出从无符号到已签名的警告
- 编译器警告:执行到达值返回函数的末尾而不返回值
- 在未链接的部分上生成警告
- 警告 C4552:">>":未使用表达式的结果
- 禁止显示由于常量为零而比较始终为假的警告
- C++ 警告:将新创建的 gsl::owner<> 分配给非所有者
- 双重标准?为什么只有 char* const&a = "bla" 的警告?