为啥 ostream 为定义为 `volatile char[]` 的字符串打印 `1`? [复制]
Posted
技术标签:
【中文标题】为啥 ostream 为定义为 `volatile char[]` 的字符串打印 `1`? [复制]【英文标题】:Why does ostream prints `1` for a string defined as `volatile char[]`? [duplicate]为什么 ostream 为定义为 `volatile char[]` 的字符串打印 `1`? [复制] 【发布时间】:2014-08-24 16:06:17 【问题描述】:考虑这个(人工)示例:
#include <cstdio>
#include <iostream>
int main()
volatile char test[] = "abc";
std::printf("%s\n", test);
std::cout << test << "\n";
使用 GCC 编译并运行会得到以下输出:
$ g++ test.cc
$ ./a.out
abc
1
如您所见,printf
正确打印字符串,而 cout
打印 1
。在这种情况下,为什么写信给cout
会产生1
?
【问题讨论】:
volatile char[N]
匹配 bool
优于 const char *
。实际上,它根本不匹配const char *
。
@sharth 很好,我什至没有想过要找一个骗子。它们足够接近,可以很好地合并。
【参考方案1】:
std::basic_ostream::operator<< 仅具有 const char*
或 const void*
的重载,在这种情况下不匹配,因为您不能在没有强制转换的情况下丢弃 volatile 限定符,这在draft C++ standard 部分4.4
资格转换 其中说:
“指向 cv1 T 的指针”类型的纯右值可以转换为 如果“cv2 T”比“cv1 T”更符合 cv 条件,则输入“指向 cv2 T”的指针。
所以它使用的是bool
版本,因为它不是nullptr
,所以结果是true
。
如果您从 test
中删除 volatile 限定符,这将提供您期望的结果。几个答案建议使用 const_cast
删除 volatile 限定符,但这是未定义的行为。我们可以通过转到7.1.6.1
部分看到 cv-qualifiers 段落 6 说:
如果试图引用一个用 a 定义的对象 volatile 限定类型,通过使用带有 非 volatile 限定类型,程序行为未定义。
const_cast
在这种情况下会产生一个 prvalue 但取消引用该指针会产生一个 lvalue 这将调用未定义的行为。
【讨论】:
【参考方案2】:operator<<
唯一合适的重载是bool
,因此数组被转换(通过指针)为bool
,给出true
,因为它的地址是非空的。除非您使用 std::boolalpha
操纵器,否则此输出为 1
。
它不能使用 const char *
的重载来输出字符串,或者 const void *
的重载来输出指针值,因为这些转换需要删除 volatile
限定符。隐式指针转换可以添加限定符,但不能删除它们。
要输出字符串,你必须抛弃限定符:
std::cout << const_cast<const char*>(test) << "\n";
但请注意,这会产生未定义的行为,因为数组将被访问,就好像它不是易失的一样。
printf
是一个老式的可变参数函数,没有类型安全性。 %s
说明符使其将参数解释为 const char *
,无论它实际上是什么。
【讨论】:
这在技术上合法吗?operator<<
可以在字符串不是易失性的假设下进行优化吗?例如,它可能会使用strlen(s)
来决定分配多大的缓冲区,然后使用strcpy
复制到该缓冲区。
强制转换的答案具有未定义的行为:C++14[dcl.type.cv]p6 说“如果尝试通过使用来引用具有 volatile 限定类型定义的对象具有非易失性限定类型的泛左值,程序行为未定义。”您通常只能通过元素上的手写循环与 volatile 数组进行交互。
@JeffreyYasskin 我对此感到疑惑,但据我所知,const_cast
在这种情况下会产生prvalue
,但我还没有详细考虑过。
@ShafikYaghmour:const_cast
确实产生了一个prvalue const char*
指向易失性数组的第一个元素([expr.const.cast])。但这仅仅意味着 pointer 是一个右值,而不是它指向的任何东西。取消引用该指针会产生一个引用该第一个元素 ([expr.unary.op]) 的左值,这会产生未定义的行为。
@RaymondChen:是的,你是对的,它会给出未定义的行为。我已添加警告。【参考方案3】:
通过最少的网络搜索找到here 的答案:
简答:
cout
将对象解释为bool
,因为volatile
限定符。这是<<
运算符的重载怪癖。长答案:在没有显式转换的情况下,无法将易失性指针转换为非易失性指针,因此在调用
<<
运算符时,既不能使用char*
也不能使用void*
重载。没有 volatile 限定的重载,最接近的匹配是bool
重载,因此您的数组被解释为布尔值而不是地址或字符串。您可以通过多种方式修复它,但显式转换可能是您想要的:
std::cout<< (char*)test <<std::endl;
(我个人会投给const char*
。)
【讨论】:
【参考方案4】:volatile
限定符将其转换为 bool
,请尝试:
std::cout << const_cast<char*>(test) << "\n";
【讨论】:
此答案具有未定义的行为:C++14[dcl.type.cv]p6 表示“如果尝试通过使用具有非 volatile 限定类型的 glvalue,程序行为未定义。" @JeffreyYasskin 猜这个变量相当...volatile
! ;)以上是关于为啥 ostream 为定义为 `volatile char[]` 的字符串打印 `1`? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
为啥 ostream::write() 在 C++ 中需要“const char_type*”而不是“const void*”?