为啥 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&lt;&lt; 唯一合适的重载是bool,因此数组被转换(通过指针)为bool,给出true,因为它的地址是非空的。除非您使用 std::boolalpha 操纵器,否则此输出为 1

它不能使用 const char * 的重载来输出字符串,或者 const void * 的重载来输出指针值,因为这些转换需要删除 volatile 限定符。隐式指针转换可以添加限定符,但不能删除它们。

要输出字符串,你必须抛弃限定符:

std::cout << const_cast<const char*>(test) << "\n";

但请注意,这会产生未定义的行为,因为数组将被访问,就好像它不是易失的一样。

printf 是一个老式的可变参数函数,没有类型安全性。 %s 说明符使其将参数解释为 const char *,无论它实际上是什么。

【讨论】:

这在技术上合法吗? operator&lt;&lt; 可以在字符串不是易失性的假设下进行优化吗?例如,它可能会使用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 限定符。这是&lt;&lt; 运算符的重载怪癖。

长答案:在没有显式转换的情况下,无法将易失性指针转换为非易失性指针,因此在调用 &lt;&lt; 运算符时,既不能使用 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`? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

C++中cout<<字符数组名;为啥能输出字符串?

为啥这个 volatile 变量的地址总是为 1?

为啥将 volatile 与同步块一起使用?

C# bool 是原子的,为啥 volatile 有效

为啥 ostream::write() 在 C++ 中需要“const char_type*”而不是“const void*”?

第四周:运算符重载