reinterpret_cast 如何工作?

3
我想知道reinterpret_cast在幕后是如何工作的。我正在从一本书中学习它,但我不理解。 例如,假设我有以下代码部分:
int a = 255;
char *pChar = reinterpret_cast<char*>(&a);

或者

std::string str = "Hello";
char *pChar = reinterpret_cast<char*>(&str);

在这两个示例中,pChar将指向什么?为什么我尝试打印它们的内容时看不到任何东西?当然,reinterpret_cast是如何工作的?

编辑: 我知道reinterpret_cast使用起来非常危险,只想用它将一个内存块直接写入二进制文件中。我不明白的是,当我有一个

int a = 255; (00 00 00 FF in memory)

我希望将变量a视为一系列字节,即char*:

char *pChar = reinterpret_cast<char*>(&a);

你好,我会翻译中文。以下是关于编程的内容:

pChar指向变量a(00 00 00 FF)的各个字节吗?

当我想要写入二进制文件时,pChar所指向的内容会被写入吗?

a_file.write(reinterpret_cast<char*>(&a), sizeof(a));

它会写入变量 a 的各个字节,对吗?

C++编译器和运行时库将把任何char*视为C风格的以null结尾的字节字符串。如果它实际上不是这样,那么你就不能像打印(或者其他处理)这样的字符串一样对待它。 - Some programmer dude
1
reinterpret_cast<char*>(&a) 告诉编译器:“我们都知道 &a 不是一个 char*,但让我们假装它是。” - Pete Becker
1
作为学习C++的人,应该避免使用reinterpret_cast。了解它是好的;然而,在您的代码中使用此转换的相对较少的地方会导致未定义的行为。 - Richard Critten
1
@Someprogrammerdude 嗯,什么?!虽然C标准库的字符串函数通常会使用预期包含NUL终止字符串的char *参数,但这并不是char *类型本身固有的特性,编译器也不会做出这种假设,除非解析字符串字面值。 - Alnitak
2个回答

6
它在运行时不执行任何操作。Cpp reference

与 static_cast 不同,但类似于 const_cast,reinterpret_cast 表达式不会编译成任何 CPU 指令。它完全是一种编译器指令,指示编译器将表达式的位序列(对象表示)视为具有类型 new_type。

你的两个转换都相当危险,因为第一个将指针指向 int,这很可能在内存中表示为 00 00 00 FF,因此不会打印任何内容,因为 00 == '\0',这是字符串结束符。这假定您使用的是大端机器。如果这是一个填充了所有字节的非零值的 int,则会无限地读取该位置之后的内容。
第二句话告诉编译器将string所在的位置视为char*,这不是实际字符串内容的起始地址,而是一个实现定义的结构体,该结构体可能包含大小、容量和指针变量或小字符串优化的字符串表示。由于大小和容量通常为64位宽,并且大小和容量可能都小于2^32,因此您可能会遇到零字节,从而什么也不打印。再次强调,如果没有意外的零字节,您将无限制地读取超出范围。 针对OP的编辑: 根据链接cpp-reference网站的第5节
任何类型为T1的对象指针都可以转换为另一种类型cv T2的对象指针。这完全等同于static_cast(static_cast(expression))(这意味着如果T2的对齐要求不比T1的严格,则指针的值不会改变,并且将结果指针转换回其原始类型会产生原始值)。在任何情况下,只有符合类型别名规则才能安全地解引用结果指针(见下文)。
当一个指向动态类型为DynamicType的对象的指针或引用进行reinterpret_cast(或C风格转换)到不同类型AliasedType的对象的指针或引用时,转换总是成功的,但是如果以下情况之一成立,则只能使用所得到的指针或引用来访问该对象:... 如果AliasedType是char、unsigned char或std::byte,则允许将任何对象的对象表示作为字节数组进行检查。..., 该指针应指向a开始的地址。

SBO字符串可以重用大小和/或容量字段作为字符串本身的一部分。但是,SBO字符串将在其自身的某个位置存储一个空值,因此这并不重要。另一方面,字符串可以实现为3个指针,而不是指针、大小和容量。 - Yakk - Adam Nevraumont
是的,我在那里留下了一个“可能”的原因,因为我不确定当前的实现情况。我听说过至少一种可能的实现方式,它将23个字节存储在size、pointer、cap的24个字节中,重复利用最后一个字节变成零作为容量的事实,同时也兼作终止符'\0'。我认为在这种特殊情况下所有字节都可以是非零的,因为第一个字节的一位用于区分小字符串或非小字符串,并且在size == 22时,所有字节都应该是非零的。 - midor
SBO必须具有空终止符,因为c_str()const并返回以空终止的字符串。这留下了它不是SBO的情况。 - Yakk - Adam Nevraumont
没错,因为在最大大小-1时,盖帽字节之前的字节仍然是零字节。我认为这次演讲详细阐述了这种优化:https://www.youtube.com/watch?v=kPR8h4-qZdk - midor

1

在这两个例子中,pChar将指向这些变量所驻留的内存的第一个字符。

为什么我尝试打印它们的内容时看不到任何东西?

您可能是以错误的方式进行打印。您不能将它们打印为以空值结尾的字符串(例如,a 的内部表示包含 0,这将被视为终止零)。

您可以像这样打印它们:

for (size_t i=0; i<sizeof(int); i++) {
    printf("%02x ", pChar[i]);
}
printf("\n");

这将以十六进制打印a的字符值。这样,您将看到ff 00 00 00(假设您在小端机器上)。
您可以对std::string执行相同的操作。您将看到std::string的内存表示。
(您可以使用“%c”将内容打印为char。如果将stdout重定向到文件,则会在文件中看到变量的内部表示。)
当然reinterpret_cast是如何工作的?
它只是重新解释其参数,假装它具有另一种类型。没有运行时成本(注意:此解释过于简化)。 pChar会指向变量a的各个字节(00 00 00 FF)吗?
是的,假设char是一个字节,并且您在大端机器上。
它会写入变量a的各个字节,对吗?
是的,但是假设您不需要reinterpret_cast,也可以做同样的事情(假设a_file.write的第一个参数是void *)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接