【问题标题】:Why does std::cout convert volatile pointers to bool?为什么 std::cout 将 volatile 指针转换为 bool?
【发布时间】:2026-02-12 17:35:01
【问题描述】:

如果您尝试 cout 指向 volatile 类型的指针,甚至是您通常期望 cout 打印字符串的 volatile char 指针,您将改为简单地得到 '1'(假设指针不为 null 我认为)。我假设输出流 operator

示例代码:

#include <iostream>
#include <cstring>

int main()
{
    char x[500];
    std::strcpy(x, "Hello world");

    int y;
    int *z = &y;

    std::cout << x << std::endl;
    std::cout << (char volatile*)x << std::endl;

    std::cout << z << std::endl;
    std::cout << (int volatile*)z << std::endl;

    return 0;
}

输出:

Hello world
1
0x8046b6c
1

【问题讨论】:

    标签: c++ pointers iostream volatile standards-compliance


    【解决方案1】:

    不是答案

    这只是问题和答案的措辞问题。问题的出现是由于无法将 指向 volatile 对象的指针转换为 void 指针,而不是 volatile 指针

    相当重要的区别在于哪个存储元件是易失性的。问题中,指针不是volatile的(可以缓存,改变的时候不用刷到内存),而是指向的内存:

    int volatile * p = f();
    p++;      // this does not affect the perceived state of the c++ memory model
    ++p;
    *p = 5;   // this changes the perceived state
    

    之所以重要,是因为对于指向内存的 volatile 指针,该指针本身是需要特殊处理的指针。

    void foo( int * );
    
    int * volatile p = f();  // 1
    foo(p);                  // 2
    int volatile * q = f();
    //foo(q);    // error, cannot convert pointer to volatile to pointer to non-volatile
    *q = 5;                  // 3
    q = 0;                   // 4
    

    在上面的代码中,标记为 1 和 2 的操作一直到内存。 [1] 中的赋值必须转储到内存中。即使p 的值在寄存器中,它也会从[2] 处的内存中加载。标记为[3]的操作修改了q指向的值,即volatile,并将一直到主存,而操作[4]只影响指针,而不是volatile本身,并且作为这不是 c++ 内存模型可感知状态的一部分,可以在寄存器中执行(请注意,编译器可以优化掉 q 并在寄存器中执行操作,而不能优化 p

    【讨论】:

      【解决方案2】:

      我认为问题不是指向 volatile 类型的指针的显式重载,而是指向 volatile 类型的指针缺少重载。编译器无法从指针中隐式删除 volatile 限定符,因此它会检查可用的重载,选择 operator

      【讨论】:

      • 同样,这应该读作“指向易失性对象类型的指针”。区别是至关重要的:void f( int * p ); int main() { int x = 5; int * volatile p = &amp;x; f(p); } 指针的 volatile-ness 并不会真正影响调用:它将 volatile-ly 被读取并复制到函数的参数,因此将传递给 f(公平地说我会投票给你,因为所有其他答案确实有同样的问题——即使他们添加了其他东西......)
      【解决方案3】:

      我认为原因是 volatile 指针不能隐式转换为 void *。这在标准的附录 C 中,其基本原理是类型安全。

      更改:仅指向非 const 和 非易失性对象可能是隐式的 转换为 void* 理由:这 提高类型安全性。

      因此,您无需转换为 void *(将以十六进制打印),而是将“默认”转换为 bool。

      【讨论】:

      • +1,第一句应该是'pointers to volatile'而不是'volatile pointer' :)
      • 附加的附录总是让我感到好奇。由于在 C 中,您也无法将 T const* 转换为 void*。上次我查的时候,你也需要一个演员表。
      【解决方案4】:

      ostream::operator&lt;&lt; 具有以下重载:

      ostream& operator<< (bool val );
      ostream& operator<< (const void* val );
      

      当你传入一个 volatile 指针时,第二个重载不能应用,因为 volatile 指针不能在没有显式转换的情况下转换为 non-volatile。但是,任何指针都可以转换为bool,所以选择第一个重载,你看到的结果是1或者0。

      因此,这样做的真正原因不是代表标准委员会的有意决定,而只是标准没有指定采用 volatile 指针的重载。

      【讨论】:

      • +1 更准确地说是指向易失性内存的指针,易失性指针将是 char * volatile 而不是 char volatile *
      • 当然,你可以添加一个免费的函数重载operator&lt;&lt;(ostream &amp;os, volatile void*p) { return os &lt;&lt; const_cast&lt;void*&gt;(p); } 并完成它。
      • @Potatoswatter 抛弃指向对象的易失性以访问对象,就好像它是非易失性的is undefined behavior,不幸的是。 (除非指向的对象wasn't originally volatile。)
      • @EliahKagan 在指针类型中抛弃volatile,并打印指针的值,将不会访问对象。没有UB。另外,您是在 Wellwood 中学就读的 Eliah Kagan 吗?
      • @Potatoswatter 你是对的——只是打印地址甚至不会取消引用指针,所以你的重载是完全安全的,我的批评是错误的。对于那个很抱歉!我应该说的是volatile char*--which people sometimes end up being told to make and use--的类似重载会产生未定义的行为,不应该用于打印易失性文本。 (当人们想知道为什么他们的 volatile 字符串不打印时,经常会出现将指向 volatile 的指针传递给 operator&lt;&lt; 的话题。)/ 是的,我就是 Eliah Kagan。