这个说法正确吗?指针的任何“类型”都可以指向任何其他类型吗?因为我这么相信,还是有疑问的。
为什么要为明确的类型声明指针?例如int
或char
?
我能得到的一个解释是:如果一个int
类型指针指向一个char
数组,那么当指针递增时,指针将从 0 位置跳转到 2 位置,在两者之间跳过 1 个位置(因为 int size=2)。
也许是因为指针只保存一个值的地址,而不是值本身,即int
or double
。
我错了吗?这种说法正确吗?
指针可以互换,但不是必须的。
特别是,在某些平台上,某些类型需要与某些字节边界对齐。因此,虽然 achar
可能在内存中的任何位置,但 anint
可能需要位于 4 字节边界上。
另一个重要的潜在区别是函数指针。
在许多平台上,指向函数的指针可能无法与指向数据类型的指针互换。
值得重复:这是特定于平台的。
我相信英特尔 x86 架构对所有指针都一视同仁。
但是您很可能会遇到其他不正确的平台。
每个指针都是某种特定类型。有一种特殊的通用指针类型void*
可以指向任何对象类型,但您必须先将 a 转换void*
为某种特定的指针类型,然后才能取消引用它。(我忽略了函数指针类型。)
您可以将指针值从一种指针类型转换为另一种。在大多数情况下,将指针从foo*
to转换bar*
回 tofoo*
将产生原始值——但这实际上并不能保证在所有情况下。
您可以使 type 的指针指向 typefoo*
的对象bar
,但是 (a) 这通常是个坏主意,并且 (b) 在某些情况下,它可能不起作用(例如,如果目标类型foo
并且bar
具有不同的大小或对齐方式要求)。
你可以摆脱这样的事情:
int n = 42;
char *p = (char*)&n;
这导致p
指向n
- 但*p
没有给你的值n
,它给你的第一个字节的值n
作为 a char
。
指针运算的不同行为只是具有不同指针类型的部分原因。它主要是关于类型安全。如果你有一个 type 的指针int*
,你可以合理地确定(除非你做了一些不安全的事情)它实际上指向一个int
对象。如果您尝试将其视为不同类型的对象,编译器可能会抱怨它。
基本上,我们有不同的指针类型,原因与我们有其他不同的类型的原因相同:因此我们可以在编译器的帮助下跟踪每个对象中存储的值类型。
(有些语言只有无类型的泛型指针。在这种语言中,更难避免类型错误,例如存储一种类型的值并意外访问它,就好像它是另一种类型一样。)
任何指针都可以引用内存中的任何位置,因此从技术上讲,该语句是正确的。话虽如此,在重新解释指针类型时需要小心。
一个指针基本上有两条信息:一个内存位置,以及它期望在那里找到的类型。内存位置可以是任何东西。它可能是存储对象或值的位置;它可能在一串文本的中间;或者它可能只是一个任意的未初始化内存块。
不过,指针中的类型信息很重要。您问题中的数组和指针算术解释是正确的 - 如果您尝试使用指针迭代内存中的数据,那么类型需要正确,否则您可能无法正确迭代。这是因为不同的类型有不同的大小,并且可能有不同的对齐方式。
就程序中数据的处理方式而言,类型也很重要。例如,如果您有一个int
存储在内存中,但您通过取消引用float*
指针来访问它,那么您可能会得到无用的结果(除非您出于特定原因以这种方式对其进行了编程)。这是因为 aint
在内存中的存储方式与 afloat
的存储方式不同。
指针的任何“类型”都可以指向任何其他类型吗?
一般没有。类型必须相关。
可以使用reinterpret_cast
将指针从一种类型转换为另一种类型,但除非这些指针可以使用 a 合法转换,否则static_cast
无效reinterpret_cast
。Foo* foo = ...; Bar* bar = (Bar*)foo;
因此,除非Foo
并且Bar
实际上是相关的,否则你不能这样做。
您还可以使用reinterpret_cast
to 从对象指针转换为 a void*
,反之亦然,从这个意义上说, avoid*
可以指向任何东西——但这似乎不是您要问的。
此外,您可以reinterpret_cast
从对象指针到整数值,反之亦然,但同样,这不是您所要求的。
最后,为char*
. 您可以使用任何其他类型的地址初始化char*
变量,并对结果指针执行指针数学运算。如果指向的东西实际上不是 a ,您仍然无法通过指针取消引用char
,但它可以被转换回实际类型并以这种方式使用。
另请记住,每次reinterpret_cast
在任何情况下使用时,您都是在悬崖峭壁上跳舞。Foo
当它实际指向的东西是a 时,取消引用指向 a 的指针会Bar
在类型不相关时产生未定义的行为。你最好不惜一切代价避免这些类型的演员表。
首先,并不是所有的指针都必须是同一个东西。例如,函数指针可能与数据指针非常不同。
旁白:PPC 上的函数指针
在 PPC 平台上,这很明显:一个函数指针实际上是两个指针,因此根本没有办法将函数指针有意义地转换为数据指针或返回。即以下将成立:
int* dataP; int (*functionP)(int); assert(sizeof(dataP) == 4); assert(sizeof(functionP) == 8); assert(sizeof(dataP) != sizeof(functionP)); //impossible: //dataP = (int*)functionP; //would loose information //functionP = (int (*)(int))dataP; //part of the resulting pointer would be garbage
此外,对齐存在问题:根据平台,某些数据类型可能需要在内存中对齐。这在矢量数据类型中尤其常见,但可以应用于任何大于字节的类型。例如,如果 aint
必须是 4 字节对齐,则以下代码可能会崩溃:
char a[4];
int* alias = (int*)a;
//int foo = *alias; //may crash because alias is not aligned properly
如果指针来自malloc()
调用,这不是问题,因为这可以保证为所有类型返回充分对齐的指针:
char* a = malloc(sizeof(int));
int* alias = (int*)a;
*alias = 0; //perfectly legal, the pointer is aligned
最后,还有严格的别名规则:您不能通过指向另一种类型的指针访问一种类型的对象。禁止使用双关语:
assert(sizeof(float) == sizeof(uint32_t));
float foo = 42;
//uint32_t bits = *(uint32_t*)&foo; //type punning is illegal
如果您绝对必须将位模式重新解释为另一种类型,则必须使用memcpy()
:
assert(sizeof(float) == sizeof(uint32_t));
float foo = 42;
uint32_t bits;
memcpy(&bits, &foo, sizeof(bits)); //bit pattern reinterpretation is legal when copying the data
为了允许memcpy()
和朋友实际可实现,C/C++ 语言标准为char
类型提供了一个例外:您可以将任何指针强制转换为 a char*
,将数据复制char
到另一个缓冲区,然后以其他类型访问该另一个缓冲区。结果是实现定义的,但标准允许。用例主要是一般的数据操作例程,如 I/O 等。
指针的可互换性比您想象的要少得多。除了 to/from 之外,不要以任何其他方式重新解释指针char*
(检查“from”情况下的对齐方式)。即使这样也不适用于函数指针。