Char到Int指针转换不工作

Char to Int Pointer Casting Not Working

本文关键字:工作 转换 指针 Int Char      更新时间:2023-10-16

我对char to int pointer的铸造感到困惑。我正在检查指针的转换是如何工作的,下面的代码int to char工作正常。

#include <iostream>
using namespace std;
int main(){
    int a=65;
    void *p=&a;
    cout << *static_cast<char*>(p);
}

输出
A

但是当我试图从char to int投射时,它没有显示正确的值。

#include <iostream>
using namespace std;
int main(){
    char a='A';
    void *p=&a;
    cout << *static_cast<int*>(p);
}

上面的代码有什么问题?输出是关于垃圾值的。

首先,您必须了解x86架构是所谓的little-endian。这意味着在多字节变量中,字节在内存中从最小到最大排序。如果你不明白这是什么意思,马上就会明白了。

A char是8位——一个字节。当您将'A'存储到其中时,它将获得0x41的值并且很高兴。int更大;在许多体系结构中,它是32位——4字节。当将值'A'赋值给int类型时,它将获得值0x00000041。这在数字上是完全相同的,但是在int中有三个额外的零字节。

你的int包含0x00000041。在内存中,这是以字节为单位排列的,因为你是在一个小端架构上,这些字节从最小到最大排列——与我们通常写它们的相反!内存实际上是这样的:

      +----+----+----+----+
int:  | 41 | 00 | 00 | 00 |
      +----+----+----+----+
      +----+
char: | 41 |
      +----+

当你获取一个指向int的指针并将其强制转换为char*,然后对其解引用时,编译器将获取int的第一个字节——因为char只有一个字节宽——并将其打印出来。其他三个字节被忽略!现在回顾一下,注意到如果int中的字节顺序颠倒,就像在大端架构中一样,您将检索到值0 !因此,这段代码的行为——从int*char*的强制转换如您所期望的那样工作——严格依赖于您正在运行它的机器。

另一方面,当你拿一个指向char的指针并将其强制转换为int*,然后引用它时,编译器将抓取char中的一个字节,正如你所期望的那样,但随后它还将再读取三个字节,因为int s是四个字节宽!这三个字节是什么?你不知道!你的记忆是这样的:

      +----+
char: | 41 |
      +----+
      +----+----+----+----+
int:  | 41 | ?? | ?? | ?? |
      +----+----+----+----+

你在int中得到一个垃圾值,因为你正在读取未初始化的内存。在不同的平台上或在不同的行星排列下,您的代码可能工作得很好,也可能出现分段错误并崩溃。这很难说。这就是所谓的未定义行为,这是我们与编译器玩的一个危险的游戏。我们在处理这样的记忆问题时必须非常小心;没有什么比不确定代码更可怕的了。

您可以安全地将任何内容表示为char的数组。反之则不然。这是STRICT ALIASING规则的一部分。

你可以在其他问题中阅读严格混叠:什么是严格的混叠规则?

与你的问题更密切相关:再一次:严格混叠规则和char*

引用这里给出的答案:什么是严格混叠规则?

[…解引用一个别名另一个不兼容类型的指针是未定义的行为。不幸的是,你仍然可以这样编码,也许会得到一些警告,让它编译得很好,只是在运行代码时出现奇怪的意外行为。

也与你的问题相关:再一次:严格的混叠规则和char*

C和c++都允许通过char *访问任何类型的对象(特别是char类型的左值)。它们不允许通过任意类型访问char对象。所以,是的,这个规则是"单向"规则。

(我必须感谢@Let_Me_Be的第二个链接)

当你这样做的时候:

cout << *static_cast<int*>(p);

你实际上是在说p指向一个整数(在内存中由4个字节表示),但是你之前刚刚在它里面写了一个字符(在内存中由1个字节表示),所以当你将它转换为一个整数时,你将变量扩展到3个垃圾字节。

但是如果你把它转换回char类型你会得到a,因为你把int类型切成了char类型:

cout << (char) *static_cast<int*>(p);

否则,如果你只想要ASCII值,将void*转换为char*(所以当你对它解引用时,你只访问1个字节),并将其中的内容转换为int。

char a = 'A';
void *p=&a;
cout << static_cast<int>(*((char*)p));

事实是,静态强制转换能够理解你想要将一个char转换为int(并获得他的ASCII值),但是当要求一个char*转换为int*时,他只是在你解引用它时改变读取的字节数。

根据标准,将char(或多个char)转换为int是未定义的行为,因此任何结果都是允许的。大多数编译器将尝试做有意义的事情,因此以下是您在特定架构中看到的行为的可能原因:

假设一个32位的int,一个int的大小等于4个char s

不同的体系结构会以不同的方式处理这四个字节,将它们的值转换为整型,最常见的是小端序或大端序

:

[Byte1][Byte2][Byte3][Byte4]

int值可以是:

(Little Endina) Byte1+Byte2*256+Byte3*256^2+Byte4*256^3
(Big Endian   ) Byte4+Byte3*256+Byte2*256^2+Byte1*256^3

在你的情况下,Byte1或Byte4被设置,剩下的字节是内存中发生的任何事情,因为你只保留一个字节,你需要4

尝试以下操作:

int main(){
    char a[4]={'A', 0, 0, 0};
    void *p=a;
    cout << *static_cast<int*>(p);    
}

您可能必须将初始化切换到{0,0,0, 'A'}以获得基于架构的您想要的

如前所述,这是一个未定义的行为,但应该与大多数编译器一起工作,并使您更好地了解底层发生的事情

考虑以下代码:

#include <iostream>
#include <iomanip>
using namespace std;
int main(){
  {
    int a=65;
    cout << hex << static_cast<int>(a) << "n";
    void *p=&a;
    cout << hex << setfill('0') << setw(2 * sizeof(int)) << *static_cast<int*>(p) << "n";
  }
  {
    char a='A';
    cout << hex << static_cast<int>(a) << "n";
    void *p=&a;
    cout << hex << *static_cast<int*>(p) << "n";
  }
}

在输出中确实有'A'字符代码(0x41),但是它被填充到int的大小,并且没有初始化值。您可以在输出变量的十六进制值时看到它。