任何类型的指针都可以指向任何东西吗?

8
这个说法正确吗?任何“类型”的指针都可以指向任何其他类型吗? 因为我相信是这样的,还有一些疑问。
为什么需要声明明确类型的指针?例如:int或char?
我能想到的一个解释是:如果一个int类型的指针指向一个char数组,那么当指针被增加时,指针将从第0个位置跳到第2个位置,中间跳过第1个位置(因为int大小为2)。
也许是因为指针只保存值的地址,而不是值本身,即int或double。
我错了吗?那个说法正确吗?

4
任何一种指针都能指向任何其他类型的指针吗?不行。 - juanchopanza
2
任何类型的指针都可以指向其他类型吗?这是一个问题,而不是陈述。 - Paddyd
3
看起来你理解得很好。如果你对指针的类型进行转换,那么你可以将任何指针分配给任何地址,但结果可能是不正确的。 - Kevin Panko
3
为什么被踩了?这实际上是一个很好的问题。 - DrakaSAN
4
我不确定为什么人们要投票关闭这个问题。我认为这是一个关于编程的问题,具体涉及指针语义。 - Keith Thompson
显示剩余8条评论
5个回答

7

指针可能是可互换的,但不一定必须互换。

特别地,在某些平台上,某些类型需要对齐到特定的字节边界。因此,虽然char可以在内存中的任何位置,但int可能需要在4字节边界上。

另一个重要的潜在差异是函数指针。
在许多平台上,指向函数的指针可能无法与指向数据类型的指针互换。

需要强调的是:这是特定于平台的

我相信Intel x86体系结构将所有指针视为相同的。但是您可能会遇到其他平台,该语句不成立。


我强烈怀疑在任何平台上,将一个指针从一种类型转换为另一种类型不会修改地址值。我知道解引用未对齐的指针可能会导致异常,但我怀疑编译器在进行转换时会对齐指针。我看不到这种行为的任何好处或动机。 - Spidey
指针是否被不同对待?尽管所有指针无论其类型都仅用于存储该位置的内存地址,但这些内存地址是否因类型而异? - Abhinav Gauniyal
我可以设计一台机器,其中所有字符都存储在16位地址空间中,整数存储在不同的24位地址空间中,其他类型存储在单独的32位地址空间中,可执行代码则在64位地址空间中。从C++的角度来看,这样的设计是完全合法的,各种指针彼此完全不兼容。 - abelenky
4
这让我想起了在我的英特尔电脑上编写作业程序的日子,当时一切都运行良好,但后来发现在教授的UNIX工作站上程序会崩溃。 - Kevin Panko
是的,第一次在非英特爾平台上遇到對齊問題時,我感到非常震驚和困惑了好幾天。現在,辨識它們已經幾乎成為了本能。 :) - abelenky

4
每个指针都属于某个特定类型。有一种特殊的通用指针类型 void*,它可以指向任何对象类型,但在您可以对其进行解引用之前,必须将 void* 转换为某个特定的指针类型。(我忽略了函数指针类型。)
您可以将一个指针值从一个指针类型转换为另一个。在大多数情况下,将一个指针从 foo* 转换为 bar*,然后再转换回 foo*,会产生原始值——但实际上并不保证在所有情况下都是这样。
您可以使类型为 foo* 的指针指向类型为 bar 的对象,但是(a)这通常是一个坏主意,而且(b)在某些情况下,可能行不通(例如,如果目标类型 foobar 具有不同的大小或对齐要求)。
您可以使用类似以下方式的语句:
int n = 42;
char *p = (char*)&n;

这会导致p指向n -- 但是*p不会给你n的值,它将会把 n 的第一个字节作为char类型来输出。
指针算术的不同行为只是拥有不同指针类型的原因之一。这主要涉及到类型安全问题。如果你有一个int*类型的指针,你可以相对确信(除非你做了不安全的操作)它实际指向一个int对象。如果你试图将其作为另一种类型的对象处理,编译器通常会发出警告。
基本上,我们有不同的指针类型,和我们有其他不同类型的原因一样:我们可以通过编译器跟踪每个对象中存储的值的种类。
(有一些只有无类型通用指针的语言。在这种语言中,更难避免类型错误,例如存储一个类型的值并意外地将其作为另一种类型访问。)

1
@JohnDibling 强制转换是有效的。char* 可以别名任何其他指针类型。 - Simple
1
@Simple:你说得对,我收回所有话。睡眠不足会导致极度混乱。 - John Dibling
除了函数指针之外,@Simple。在PPC平台上,函数指针实际上可能比普通的char*更大,因此不能与普通指针互换。 - cmaster - reinstate monica
@cmaster:没错。关于void*和函数指针之间的转换是否被允许,也存在一些争议。(在我看来,它是可以的,但是它具有未定义的行为。)但至少,将函数指针转换为void*可能会丢失信息。 - Keith Thompson

3
任何指针都可以引用内存中的任何位置,因此从技术上讲这个说法是正确的。但是,在重新解释指针类型时需要小心。
一个指针基本上有两个信息:一个内存位置和它期望在那里找到的类型。内存位置可以是任何东西。它可以是对象或值存储的位置;它可以是文本字符串中间的位置;或者它可以是未初始化内存的任意块。
指针中的类型信息是很重要的。您问题中的数组和指针算术解释是正确的——如果您尝试使用指针迭代内存中的数据,则类型必须正确,否则您可能无法正确迭代。这是因为不同类型具有不同的大小,并且可能对齐方式不同。
类型还重要的一点是程序中处理数据的方式。例如,如果您在内存中存储了一个int,但是通过取消引用float*指针来访问它,则可能会得到无用的结果(除非您出于特定原因编程)。这是因为int在内存中存储的方式与float的存储方式不同。

如果我没有执行迭代事件,甚至没有声明数组,那么编译器可能会预测,但是当我使用同一个指针首先指向Int,然后放弃它,然后下一次指向char时,它仍然会出错吗?因为我认为指针只存储地址,而且这不应该随着变量数据类型的更改而修改。 - Abhinav Gauniyal
如果您尝试将错误的类型分配给指针,例如将int的地址分配给char*指针,您的编译器可能会发出错误或警告。您可以使用reinterpret_cast<>()来覆盖错误/警告。在某些情况下,这是必要的,但绝对不是大多数程序中应该做的事情。 - Peter Bloomfield

1

有些指针比其他指针更重要...

首先,不是所有的指针都是同一种东西。例如,函数指针可以与数据指针非常不同。

Aside: Function pointers on PPC

On the PPC platform, this was quite obvious: A function pointer was actually two pointers under the hood, so there was simply no way to meaningfully cast a function pointer to a data pointer or back. I.e. the following would hold:

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

对齐

此外,存在对齐问题:根据平台的不同,某些数据类型可能需要在内存中对齐。这在向量数据类型中特别常见,但也适用于任何大于一个字节的类型。例如,如果一个 int 必须是 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类型提供了一个异常:你可以将任何指针强制转换为char*,将char数据复制到另一个缓冲区,然后将该缓冲区作为其他类型进行访问。结果是由实现定义的,但标准允许这样做。使用案例大多是一般的数据操作程序(如I/O等)。

简而言之:

指针的可替换性比你想象的要少得多。除了在char*之间进行转换(在“from”情况下检查对齐)外,不要以任何其他方式重新解释指针。即使对于函数指针也是如此。


1

任何类型的指针都可以指向其他类型吗?

通常情况下不行。类型必须相关。

可以使用 reinterpret_cast 将一个指针从一种类型转换为另一种类型,但除非这些指针可以合法地使用 static_cast 进行转换,否则 reinterpret_cast 是无效的。因此,除非 FooBar 实际上是相关的,否则不能执行 Foo* foo = ...; Bar* bar = (Bar*)foo;

您还可以使用 reinterpret_cast 将对象指针转换为 void*,反之亦然,在这个意义上,void* 可以指向任何东西——但这不是您似乎在询问的内容。

此外,您可以从对象指针到整数值进行 reinterpret_cast,反之亦然,但同样不是您所询问的内容。

最后,对于char*特别例外。您可以使用任何其他类型的地址初始化char*变量,并对生成的指针执行指针数学运算。如果被指向的东西实际上不是char,则仍然无法通过指针进行解引用,但它可以转换回实际类型并以这种方式使用。

还要记住,每当您在任何上下文中使用reinterpret_cast时,您都在悬崖边跳舞。当类型不相关时,将指针解引用为Foo,而实际上它指向的是Bar会产生未定义的行为。您最好尽可能避免使用这些类型的转换。


如果我以某种方式将指针首先指向int,然后放弃该变量并将同一指针指向另一个char变量,我不知道这是否可能,但我认为它不应该产生错误,因为指针旨在存储地址,而类型只是表示对它们执行的算术操作。先生,我有什么地方说得对吗? - Abhinav Gauniyal
2
@AbhinavGauniyal:你可以在实际指向intchar*上进行指针运算,但如果你使用该指针并且它不指向实际上是char的东西,那么你将得到未定义的行为。 - John Dibling
char* 必须实际指向一个 char 才能起作用。这是最基本的要求。 - John Dibling

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