【问题标题】:Why is address of char data not displayed?为什么不显示字符数据的地址?
【发布时间】:2023-01-26 06:22:32
【问题描述】:
class Address {
      int i ;
      char b;
      string c;
      public:
           void showMap ( void ) ;
};

void Address :: showMap ( void ) {
            cout << "address of int    :" << &i << endl ;
            cout << "address of char   :" << &b << endl ;
            cout << "address of string :" << &c << endl ;
}

输出是:

         address of int    :  something
         address of char   :     // nothing, blank area, that is nothing displayed
         address of string :  something 

为什么?

另一个有趣的事情:如果 int、char、string 是公开的,那么输出是

  ... int    :  something 
  ... char   :   
  ... string :  something_2

something_2 - something 始终等于 8。为什么? (不是 9)

【问题讨论】:

    标签: c++ cout memory-address


    【解决方案1】:

    当你获取 b 的地址时,你会得到char *operator&lt;&lt; 将其解释为 C 字符串,并尝试打印字符序列而不是其地址。

    试试cout &lt;&lt; "address of char :" &lt;&lt; (void *) &amp;b &lt;&lt; endl

    [编辑] 就像 Tomek 评论的那样,在这种情况下更合适的转换是 static_cast,这是一个更安全的选择。这是一个使用它而不是 C 风格转换的版本:

    cout << "address of char   :" << static_cast<void *>(&b) << endl;
    

    【讨论】:

    • 你能举例说明为什么 static_cast 在这里是更安全的选择吗?我不明白在这里使用 void * 有什么危害。
    • @VishalSharma 如果您知道b 是什么,那么这里真的没有。但是当 b 时,C++ 转换为您提供额外的安全性不是你认为它是什么; C 演员只会盲目地照你说的去做,而不是真正关心,这是不好的。
    • 这里的目标是否只是为了灌输良好的实践,因为即使我不知道 b 是什么,我仍然不明白 cout<<(void *)&b 是如何不好的?无论如何它应该只打印地址,不是吗?
    • @VishalSharma 是的,避免 C 风格的转换是一个好习惯。在这种特殊情况下的行为将是相同的。在其他情况下,&amp; 可能已经过载(因此您没有得到“地址”),或者您可能正在做一些 const/volatile-正确性很重要的事情,或者,或者,或者
    【解决方案2】:

    有2个问题:

    • 为什么它不打印字符的地址:

    打印指针将打印int*string*的地址,但不会打印char*的内容,因为operator&lt;&lt;中有一个特殊的重载。如果您想要地址,请使用:static_cast&lt;const void *&gt;(&amp;c);

    • 为什么intstring的地址差是8

    在你的平台上,sizeof(int)4sizeof(char)1,所以你真的应该问为什么8不是5。原因是字符串在 4 字节边界上对齐。机器使用单词而不是字节工作,如果单词不因此“拆分”这里几个字节和那里几个字节,那么机器工作得更快。这就是所谓的结盟

    您的系统可能与 4 字节边界对齐。如果您有一个 64 位系统和 64 位整数,则差值为 16。

    (注意:64 位系统一般指的是指针的大小,而不是 int。所以 64 位系统具有 4 字节的 int 仍然会有 8 的差异,因为 4+1 = 5 但四舍五入为 8 . 如果 sizeof(int) 为 8 则 8+1 = 9 但四舍五入为 16)

    【讨论】:

      【解决方案3】:

      当您将 char 的地址流式传输到 ostream 时,它会将其解释为 ASCIIZ“C 样式”字符串的第一个字符的地址,并尝试打印假定的字符串。您没有 NUL 终止符,因此输出将继续尝试从内存中读取,直到恰好找到一个或操作系统将其关闭以尝试从无效地址读取。它扫描的所有垃圾都将发送到您的输出。

      您可能可以通过转换它来显示您想要的地址,如(void*)&amp;b

      重新调整结构中的偏移量:您观察到字符串位于偏移量 8 处。这可能是因为您有 32 位整数,然后是 8 位字符,然后编译器选择再插入 3 个 8 位字符,以便字符串对象将在 32 位字边界处对齐。许多 CPU/内存架构需要指针、整数等位于字大小边界上以对其执行高效操作,否则在能够使用这些值之前必须执行更多操作来从内存中读取和组合多个值在一次手术中。根据您的系统,可能是每个类对象都需要从单词边界开始,或者可能是 std::string 特别是从 size_t、指针或其他需要这种对齐的类型开始。

      【讨论】:

        【解决方案4】:

        因为当您将 char* 传递给 std::ostream 时,它将打印它指向的 C 样式(即:char 数组,char*)字符串。

        请记住 "hello"char*

        【讨论】:

        • “你好”是const char[6]
        • @MSalters:不。它是char[6],使用时会衰减到char*
        • 它仅在 C 中为 char[6],但在 C++ 中为 const char[6]。有趣的是,它仍然可以衰减到 char *(与 C 的向后兼容性)。
        • @hrnt:这在 C++03 中已弃用,并在 C++11 中完全删除。
        【解决方案5】:

        char 的地址被视为以 nul 结尾的字符串,并显示该地址的内容,该地址可能未定义,但在本例中为空字符串。如果你将指针指向void *,你将得到你想要的结果。

        something2 和 something 8 之间的区别是由于对齐和编译器自行决定在堆栈中声明变量的位置的能力。

        【讨论】:

        • 由于没有构造函数,是否会自动创建一个默认构造函数,它将设置 b = 0 因此自动 null 终止?也+1
        • @Muggen:上面的代码并不完整,所以谁知道提供了什么构造函数。
        • @Muggen:不,生成的默认构造函数不会对 b 进行零初始化。你必须明确地这样做;例如Address()(作为临时地址),new Address()(与新地址比较),Address var = Address(), (in 0x) Address var {}; (我相信,需要仔细检查),或具有静态存储持续时间(函数/命名空间/全局静态)的地址对象。
        【解决方案6】:

        对于第二个问题——编译器默认会填充结构成员。默认填充为 sizeof(int),4 字节(在大多数体系结构上)。这就是为什么 int 后跟 char 将占用结构中的 8 个字节,因此 string 成员位于偏移量 8。

        要禁用填充,请使用 #pragma pack(x),其中 x 是以字节为单位的填充大小。

        【讨论】:

        • 由于对齐要求,我怀疑打包只会将字符串的地址放在五个字节的偏移量处(在许多编译器上)。
        • 数据对齐不是特定于平台的吗?另外,据我所知,int 不是标准的 4 个字节。
        • @Muggen - 数据对齐确实是特定于平台的,但最常见的是sizeof(int) - 本机 CPU 大小。在 32 位 CPU 上,这是 4 个字节。
        • @Christopher - 偏移量不是 5 个字节,而是 3。int 是从地址 0 到 3。char 应该是从 4 到 5,而不是从 4 到 7。最后 string 从8.
        • @Eli:char 位于字节 4。字节 5 到 7 是填充,不是 char 的一部分,根据定义它有 sizeof(char)==1。我指的是相对于封闭对象开头的偏移量 5。
        【解决方案7】:

        你的语法应该是

        cout << (void*) &b
        

        【讨论】:

          【解决方案8】:

          hrnt 关于空白的原因是正确的:&amp;b 的类型为char*,因此在第一个零字节之前作为字符串打印。大概b 为 0。如果将 b 设置为“A”,那么您应该期望打印输出是一个以“A”开头的字符串,并继续垃圾直到下一个零字节。使用static_cast&lt;void*&gt;(&amp;b) 将其打印为地址。

          对于你的第二个问题,&amp;c - &amp;i 是 8,因为 int 的大小是 4,char 是 1,字符串从下一个 8 字节边界开始(你可能是在 64 位系统上)。每种类型都有特定的对齐方式,C++ 根据它对齐结构中的字段,适当地添加填充。 (经验法则是大小为 N 的基本字段与 N 的倍数对齐。)特别是,您可以在 b 之后再添加 3 个 char 字段而不影响地址 &amp;c

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-11-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多