sky*_*ing 3 c pointers alignment language-lawyer
答案为“ C:何时在指针类型之间进行转换不是未定义的行为?” 表示通过没有严格对齐要求的指针来回投射是安全的。
即使类型作为标准状态不完整,对齐也很重要:
指向对象或不完整类型的指针可以转换为指向不同对象或不完整类型的指针。如果结果指针未针对指向的类型正确对齐,则该行为未定义。否则,当再次转换回时,结果应等于原始指针。当指向对象的指针转换为指向字符类型的指针时,结果指向该对象的最低寻址字节。结果的连续递增(直到对象的大小)会产生指向对象剩余字节的指针。
现在的问题是正确的对齐要求是什么struct X。当然,它是否完整取决于它的内容,但是如果它不完整(仅声明为struct X;)怎么办?或者换种说法,我们可以从头到尾投射struct X*,struct Y*然后再次获得相同的指针吗?
如果一个完整struct X和不完整的对齐要求不同,那会是一个复杂问题吗?我无法提出它们之间的实际转换,但可能是您struct X*在struct X分别完成或不完整的上下文之间传递了一个转换。还是这是始终允许的特殊情况?
首先,我认为您不能谈论不完整类型的对齐要求,因为对齐要求仅针对完整类型定义:
完整的对象类型具有对齐要求,这限制了可以分配该类型对象的地址。(第6.2.8 / 1节;所有标准引文均取自n1570.pdf,实际上是C11)
但是在有效的程序中,指针必须指向完整类型(或NULL)的对象。因此,即使指针的引用类型在某个转换单元中不完整,该指针所引用的对象(如果有)也必须具有完整的类型,因此必须具有对齐要求。但是,在所引用类型不完整的翻译单元中,无论是对于编译器还是对于编写代码的人员,都无法知道该对齐要求是什么。
因此,可以为指向不完整类型的指针合法地分配非NULL值的唯一方法是,从指向类型完整的地方(可能在另一个转换单元中)复制指向相同类型的指针。这不是转换,因为这两种类型是兼容的,并且可以保证工作。同样,指向不完整类型的指针的值的几种合法用法之一是在类型完整的上下文中将其传递给指向相同类型的指针。(其他用途包括将其与另一个指针值进行相等性比较或将其转换为整数并打印出来,但这些不涉及转换为其他指针类型。)
因此,总而言之,指向不完整类型的指针在预期的用例(引用类型不透明)的预期用例中非常有用且可用,但不能移植为任何其他类型的指针,除非可能是限定的char*和void*。
该标准不保证将指针转换为指向其对齐可能更严格的类型的指针的可能性。如果引用类型的对齐方式未知,则目标对齐方式不能更加严格的唯一指针是char*和void*。因此,将不完整类型的指针转换为char*或void*必须将其视为不可移植的类型的任何转换。
实际上,所引用类型不完整的事实并没有太大关系。该标准未指定复合类型的对齐方式。这样的对齐方式必须足以允许编译器正确对齐成员,但是它可以任意大。换句话说,不能保证这些类型:
typedef char oneChar;
struct oneChar_s { char x; };
union oneChar_u { char x; };
Run Code Online (Sandbox Code Playgroud)
具有相同的对齐方式。这两种复合类型的对齐方式都可能大于1。因此,不能保证可以将a转换oneChar*为a oneChar_s*(当然,除非oneChar*是先前反向转换的结果),并且可移植程序不会尝试。从这个意义上说,的定义struct oneChar_s是否可见没有区别。
并非巧合的是,该标准不能保证所有对象指针的大小都相同。基本理论是,在某些体系结构上,普通指针不够精确,无法引用单个字节,但是存在通过添加例如位偏移量来扩展指针的可能性。确实,可能存在其他小的对象可以打包成单词的情况,这些对象也需要增强的指针表示形式,但精度低于位偏移量。
在这样的体系结构中,不可能为小型复合对象利用不同的指针精度,因为该标准坚持认为最多有两种表示复合的指针,一种表示structs,一种表示unions:
所有指向结构类型的指针应具有相同的表示和对齐要求。指向联合类型的所有指针应具有相同的表示和对齐要求。(第6.2.5 / 27条)[注释2]
特别是,这意味着指向给定类型的对象的指针具有相同的表示形式,而不管该类型是否完整。
指针表示形式的差异不是转换为更受约束的对齐可能会失败的唯一原因。例如,一个实现可能会在转换后插入代码以验证对齐(也许是为了响应清理编译器选项)。在类型不完整的情况下,编译器将无法插入静态代码来进行检查(尽管可能进行某种运行时检查),但是编译器可能会省略验证代码这一事实不会改变不确定性。结果。
出于其价值,OP中的标准引用是C99。在C11中,已对其进行了少许修改(添加了强调以指示更改的措词):
指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针未针对引用类型正确对齐,则该行为未定义。否则,当再次转换回时,结果应等于原始指针。当指向对象的指针转换为指向字符类型的指针时,结果指向该对象的最低寻址字节。结果的连续递增(直到对象的大小)会产生指向对象剩余字节的指针。(第6.3.2.3/7节)
我认为,此更改纯属社论。它源自更改第6.2.5 / 1节中“对象类型”定义的决定。在C99中,存在三种类型:对象类型,函数类型和不完整类型。在C11中,只有两种类型-对象类型和函数类型-带有注释“在翻译单元中的各个点上,对象类型可能不完整……或完整……”,这是一个更准确的描述。
作为一个完全假设的示例,请考虑概念上与PDP-6 / 10体系结构相似的机器。这是一台字地址很大的字寻址机器。一个单词足够大,可以包含两个地址(假设的LISP实现可以利用这一事实来存储由car和组成的cons节点cdr字段成一个单词)。因为期望有效地表示小物体的矢量,所以该机器还具有可以提取或覆盖单词内的位域的指令,其中该位域指针由带有偏移和长度信息的单词指针组成。(因此,一个字只能包含一个位域指针。)(精通人士提供了一条指令,可以通过将长度加到偏移量并在必要时从下一个单词的0开始移动到位域来递增位域指针。
因此,可能存在三种不同的指针类型:
在这种假设的体系结构中,转换规则变得相当复杂。例如,您可以将转换char**为,int*因为的对齐方式int与的对齐方式相同char*。但是您无法将转换int**为,int*因为的对齐int方式大于的对齐方式int*。
除了记住这些复杂的规则外,程序员可能会选择只是避免执行指针转换,而不是保证可移植的指针转换(这是通过char*or 往返)void*。
即使没有必要,指向所有组合的指针也可以使用更大,更精确的指针类型。在我看来struct,如果不是所有复合对象,则实现似乎只会选择对s 进行最小对齐。标准的措辞将允许实现对structs 使用最小对齐方式,对所有unions 使用增强的指针表示形式。