在 C99 6.2.5 P27
所有指向结构类型的指针都应具有彼此相同的表示和对齐要求。所有指向联合类型的指针都应具有彼此相同的表示和对齐要求。指向其他类型的指针不需要具有相同的表示或对齐要求。
这是什么意思?
所有指向结构类型的指针都应具有彼此相同的表示和对齐要求。
这个例外的原因是什么?
指向其他类型的指针不需要具有相同的表示或对齐要求。
我很感激相关示例的解释。
这意味着您可以将任何指向结构的指针值存储在任何其他指向结构的变量中,并在此过程中创建一个有效的指针对象,并且您可以从中间变量中恢复原始指针。相反,不允许使用不同类别的指针作为中间体。例如:
struct Foo * p = &x; // x is a struct Foo
struct Bar * q;
memcpy(&q, &p, sizeof p); // OK, it's allowed to read (but not dereference!) q
memcpy(&p, &q, sizeof q); // OK, p is now the same it was before
union Zip * r;
int * s;
// not allowed to do the same with (p, r) and (p, s)!
memcpy
s没问题,因为它们p
都q
具有相同的大小和对齐方式,因为它们都是指向结构的指针。对于两个指向联合的指针或两个指向 int 的指针也是如此,但您不能混合类别。
这是另一个非常做作但有效的示例:
struct Foo { int a; };
int f(struct Bar * p)
{
return (struct Foo *)(p)->a;
}
int main()
{
Foo x = { 12 };
return f((struct Bar *)(&x));
}
如果函数参数是不同类别的指针(例如指向联合的指针或指向 int 的指针),则该程序将无效。f
任何对象指针可以转换为和返回的唯一指针类型是void *
. (所以我们可以制作f
a的参数void *
。这可以说是最常见的样式。但可以想象,将其作为结构指针更有效,因此在某些平台上更可取。)
这意味着将指向结构类型的指针转换为指向另一种结构类型的指针只是一种重新解释。
通常,允许指向不同类型的指针具有不同的大小和不同的对齐要求,例如,achar*
可能具有 16 的大小和对齐要求并存储地址 big-endian,但unsigned long long*
只有 8 的大小和对齐要求并且存储地址 little-endian。
但是对于struct foo *
和struct bar *
,表示和对齐要求必须相同。(与union
s 类似。)
一个原因是在 s 中有指向不完整struct
s 的指针是很常见struct
的。如果结构指针的表示和对齐要求不相同,那将是不可能的。
在非正式语言中,C 语言语义中关于指针表示和转换的重要部分是:
其他一切都取决于实施。
这些特定规则的必要性可以这样证明:
对象指针到字符指针的转换允许按字节访问,而不管类型如何。虽然 void 指针基本上是变相的字符指针,但它们具有不同的语义内涵(没有类型关联的通用指针)。
结构分别 共享表示的联合指针使指向不完整类型(不透明指针)的指针成为可能。
函数指针可能不会转换为 void 指针,因为它们可能驻留在完全不同的地址空间中,并且可能不可用于按字节访问。它们之间是可转换的,因为没有通用函数指针类型(相当于 void 指针)并且可能共享一个表示。据我所知,C 标准不需要单一表示,即原则上函数指针类型之间的转换可能涉及实际转换。但是,由于非原型函数(以及相应的函数指针)需要与所有函数类型兼容,因此可能的转换受到严重限制。
一般来说,C 标准在不人为地限制未来发展的情况下,为实现提供了很多内容以适应广泛的历史优先级。
不同的指针类型可能有不同的大小和表示;IOW, anint *
可能与 a 具有不同的大小和表示,achar *
可能具有与 a 不同的大小和表示double *
,等等。
指向任何struct
类型的指针将具有相同的大小和表示;IOW,struct T *
并且struct Q *
看起来一样。对于联合也是类似的,union T *
并且union U *
看起来相同(尽管它们可能看起来与struct
指针不同)。
例如,这意味着您可以不依赖于函数指针的结构或对齐方式。我认为这也意味着您在处理指向 C++ class
es 或struct
s 的指针时不能做出这样的假设(尽管我没有检查 C++ 是如何定义这些的)。