简单的C++指针转换

4
有人能向我解释一下这个吗:
char* a;
unsigned char* b;

b = a;
// error: invalid conversion from ‘char*’ to ‘unsigned char*’

b = static_cast<unsigned char*>(a);
// error: invalid static_cast from type ‘char*’ to type ‘unsigned char*’

b = static_cast<unsigned char*>(static_cast<void*>(a));
// everything is fine

什么是cast 2和3之间的区别?如果将3中的方法用于其他(更复杂)类型,是否存在任何陷阱?

[编辑] 一些人提到了糟糕的设计等问题...

这个简单的例子来自一个图像库,它给我指向图像数据的指针作为char*。显然,图像强度始终为正,因此我需要将其解释为unsigned char数据。


我不同意你的编辑声明。如果你得到了一个字符数组,那么它就是一个字符数组。在C++中类型是强的,而你正在进行C语言杂技表演。大多数情况下,当编译器告诉你错误时,你就是错的。 - Stephane Rolland
如果在某个时刻你真的需要无符号字符,你可以简单地这样做,但在你真正需要的非常精确的时刻:int i = 65; unsigned int j = i; 这将编译,因为它是合法的,你总是可以合法地将一个字符存储在无符号字符中。 - Stephane Rolland
那如果另一个库需要正确的 unsigned char* 呢 :D 。你会写一个循环并将所有值复制到一个新数组中吗? - tauran
是的,在这种情况下我理解,并且同意您使用reinterpret_cast<>更好。但是我不同意您使用“正确”的术语。如果强度值在0到127之间,正如显然的情况一样,那么char和unsigned char类型都是正确的。 - Stephane Rolland
7个回答

6
static_cast<void*>会破坏类型检查的目的,因为现在它指向“你不知道类型的东西”。然后编译器必须信任你,当你在新的void*上使用static_cast<unsigned char*>时,他会像你明确要求的那样尝试完成工作。如果您确实必须在此处使用转换,则最好使用reinterpret_cast<>(因为这显然显示了设计问题)。

根据此链接,使用静态转换两次并不更好? - Ali
1
@Ali:reinterpret_cast 被定义为两个 static_cast,因此安全性大致相等。使用两个 static_cast 的问题在于,会引入一个中间点,在这个点上它可能做错事情。直接使用 reinterpret_cast 表示了涉及的两种类型,因此更加简洁和难以破坏。 - GManNickG

5

您的第三种方法有效是因为C++允许通过static_cast将void指针转换为T*(并再次转换回来),但出于安全原因,对其他指针类型更加限制。 charunsigned char是两种不同的类型。这需要使用reinterpret_cast


2
另外请注意,虽然编译器会接受代码并且在我能想到的所有情况下都可以工作,但标准允许将 static_cast 转换为 void*,然后再转回相同类型,这不是您正在做的事情。这个答案是正确的:您应该明确使用 reinterpret_cast - David Rodríguez - dribeas
reinterpret_cast非常危险,而且它不是跨平台的。 - zaynyatyi
2
reinterpret_cast在这里是完全适当的...(编辑后的)问题陈述了这是一个库的解决方法,该库实际上提供了无符号字符值,但错误地在其接口中称之为字符。有数十亿行带有强制转换的C代码,它们完全可以正常工作,您只需要清楚地了解两种数据类型的位排列方式以及您要求编译器执行的操作。 - Tony Delroy

2

C++试图比C语言更加限制类型转换,所以它不允许使用static_cast将char转换为unsigned char(请注意,你将失去符号信息)。但是,类型void*是特殊的,因为C++无法对其做出任何假设,并且必须依赖编译器告诉它确切的类型(因此第三个转换有效)。

至于你的第二个问题,当然使用void*存在很多陷阱。通常情况下,你不需要使用它,因为C++的类型系统、模板等足够丰富,不需要依赖于那个"未知类型"。此外,如果你真的需要使用它,就必须非常小心地控制从void*进行的强制转换,确保插入和获取的类型确实相同(例如,不是子类的指针等)。


2

static_cast 在指针之间的转换只有在其中一个指针是 void 类型或者是将一个类的对象转换成另一个继承自该类的类的对象时才能正确工作。


+1 简短明了。此外,值得一提的是 static_cast 无法去除 const。 - sellibitze
1
标准允许您使用static_castT*转换为void*,并再次转换回T*。在标准中,将void*转换为与原始指针不同类型的结果未定义。 - David Rodríguez - dribeas

1
指针之间的转换需要使用reinterpret_cast,但是对于void*除外:
从任意指针到void*的转换是隐式的,因此您不需要显式转换:
char* pch;
void* p = pch;

从`void*`到任何其他指针的强制转换只需要使用`static_cast`:
unsigned char* pi = static_cast<unsigned char*>(p);

1
2和3之间的区别在于,在3中,您通过将其强制转换为void*来明确告诉编译器停止检查。如果对pretty much anything that isn't a direct primitive integral type使用第3种方法,您将调用未定义的行为。即使在#3中,您也可能会调用未定义的行为。 除非您确实知道发生了什么,并且如果它不是隐式转换,则几乎肯定是一个坏主意,如果您将void*转换回不是其原始类型的内容,则会出现未定义的行为。

0

注意,当你将类型转换为void*时,你会失去任何类型信息。

你试图做的事情是不正确的、错误的、容易出错的和误导性的。这就是为什么编译器返回编译错误的原因 :-)

一个简单的例子

char* pChar = NULL; // you should always initalize your variable when you declare them
unsigned char* pUnsignedChar = NULL; // you should always initalize your variable when you declare them

char aChar = -128;
pChar = &aChar;
pUnsignedChar = static_cast<unsigned char*>(static_cast<void*>(pChar));

然后,尽管 pUnsignedChar == pChar,我们仍然有 *pUnsignedChar == 255*pChar == -128

我相信这是个糟糕的玩笑,因此是糟糕的代码。


好的,但这正是强制类型转换的目的 :), 请看我的编辑 - tauran
不。reinterpret_cast 的目的,与您所做的相同,是在您确切知道对象背后的类型时使用。在您的示例中,您显然共享了信息。既然您提出这种问题,那么您显然是C++的初学者,否则您应该已经知道reinterpret_cast只能在极限情况下使用,当您真正知道自己在做什么时。 - Stephane Rolland
不,我写这篇文章是因为我知道你需要学习,所以我花了一些时间来回答你,与其他人不同。在C++中总有东西可以学习,我几乎每天都在学习......我可以向你保证,即使我已经使用C++超过10年,我仍然感觉自己比“专家”更像“中级”水平,因此我一直都是一个几乎初学者,不断学习的状态。 - Stephane Rolland
我已阅读了你的编辑。我的评论在下面,因为我完全不同意你的观点。 - Stephane Rolland
在你的问题中提到了“陷阱”,所以我给你了一个具体的例子。但是由于reinterpret_cast经常被认为是不良实践,你很快就会发现有人警告你甚至要求你修改代码。只要记住这一点:这是C++基础知识。 - Stephane Rolland
显示剩余7条评论

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