为什么所有指向结构的指针都必须具有相同的大小?

Why must all pointers to structs be of the same size?

C标准规定:

A pointer to void shall have the same representation and alignment requirements as a pointer to a character type. Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements. All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements.

sizeof(int*) 不一定等于 sizeof(char*) - 但 sizeof(struct A*) 一定等于 sizeof(struct B*).

此要求背后的基本原理是什么?据我了解,rationale behind differing sizes for basic types 是为了支持像 near/far/huge 指针 这样的用例(edit:正如在评论和在接受的答案中,这不是基本原理) - 但同样的基本原理是否适用于内存中不同位置的 structs?

答案很简单:structunion 类型可以声明为不透明类型,即:没有 structunion 细节的实际定义.如果指针的表示根据结构的细节而不同,编译器将如何确定将什么表示用于作为参数、return 值出现的不透明指针,甚至只是从内存中读取或将它们存储到内存中。

能够操纵不透明指针类型的自然结果是所有此类指针必须具有相同的表示形式。但是请注意,指向 struct 的指针和指向 union 的指针可能具有不同的表示形式,以及指向 charintdouble 等基本类型的指针...

关于指针表示的另一个区别是数据指针和函数指针之间的区别,它们的大小可能不同。这种差异在当前架构中更为常见,尽管在操作系统和设备驱动程序之外仍然很少见 space。函数指针的 64 位似乎是一种浪费,因为 4GB 应该足以用于代码 space,但是现代架构利用这个额外的 space 来存储指针签名来强化代码以抵御恶意攻击。另一个用途是利用忽略某些指针位的硬件(例如:x86_64 忽略前 16 位)来存储类型信息或使用未修改的 NaN 值作为指针。

此外,遗留 16 位代码中的 near/far/huge 指针属性未通过 C 标准中的此注释正确解决,因为所有指针都可能是 near巨大。然而,它涵盖了混合模型代码中代码指针和数据指针之间的区别,并且在某些操作系统上似乎仍然是最新的。

最后,Posix 要求所有指针具有相同的大小和表示形式,因此混合模型代码应该很快成为历史珍品。

有争议的是,不同数据类型的表示形式在当今已经非常罕见,现在是清理标准并删除此选项的时候了。主要反对意见是支持可寻址单元是大字和 8 位字节使用额外信息寻址的体系结构,使 char *void * 比常规指针更大。然而这样的架构使得指针运算非常繁琐并且也很少见(我个人从未见过)。

在丹尼斯·里奇 (Dennis Ritchie) 发明的 C 语言中,当 C 编译器遇到 struct foo *p; 的定义时,它不需要关心结构是否定义或如何定义,除非或直到程序使用指针算法或 -> 运算符。否则,它可以简单地记录 p 是指向带有标记 foo 的结构的指针,而不必知道或关心是否、在哪里或如何定义这样的结构。该标准添加了一个奇怪的小皱纹,有时会使具有匹配标签的结构指针不兼容,但问题仍然存在,编译器必须能够处理指向结构类型的指针的声明,以及这些指针之间的基本赋值,在它可能不知道结构内容的情况。

请注意,在指向具有任意对齐方式的对象的指针可能大于指向已知具有 int 对齐方式的对象的指针的平台上,编译器可能明智地指定所有结构都具有 int 对齐方式即使它们只包含角色成员。此外,此类平台的编译器可能决定以允许指向任何对象(甚至字符)的指针转换为指向包含此类对象的任何联合的指针的方式处理指向联合的指针,并用于访问联盟内的那个对象。这可能需要指向联合对象的指针是字节指针的大小,而不是更小的 int 指针。

请注意,在准标准编译器中,如果两个结构包含匹配成员,则接受 void* 并将其转换为一种结构类型的函数应该可用于对两种类型进行互换操作.不幸的是,该标准允许编译器假设代码永远不会做这样的事情,并且没有为程序员提供任何方法来指示两个结构何时可以互换使用。