从结构类型的指针派生的所有指针是否都相同? [英] Are all pointers derived from pointers to structure types the same?

查看:91
本文介绍了从结构类型的指针派生的所有指针是否都相同?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题

从结构类型的指针派生的所有指针是否都相同的问题很难回答.我认为这是一个重要的问题,主要有以下两个原因.

A..缺少指向任何"不完整或对象类型的指针的指针,对便利的函数接口施加了限制,例如:

int allocate(ANY_TYPE  **p,
             size_t    s);

int main(void)
{
    int *p;
    int r = allocate(&p, sizeof *p);
}

[完整的代码示例]

现有指向任何"不完整或对象类型的指针明确描述为:

C99/C11 §6.3.2.3 p1:

指向void的指针可以与任何不完整或对象类型的指针相互转换. [...]

从现有指向任何"不完整或对象类型的指针派生的指针,指向void的指针,严格来说是指向void的指针,并且不需要可与从指向'的指针派生的指针进行转换.任何'不完整或对象类型.


B.对于程序员而言,有意或无意地根据不需要的假设来使用约定,这些假设是有意或无意地与指针的泛化有关的,而这取决于他们对特定实现的经验.诸如可转换,可表示为整数或共享一个公共属性(对象大小,表示形式或对齐方式)之类的假设.


标准文字

根据C99 §6.2.5 p27/C11 §6.2.5 p28:

[...]指向结构类型的所有指针应具有相同的表示和对齐要求. [...]

后跟C99 TC3 Footnote 39/C11 Footnote 48:

相同的表示形式和对齐要求旨在暗示与函数的参数,函数的返回值以及并集的成员的互换性.

尽管标准未说:指向结构类型的指针",并且选择了以下词语:所有指向结构类型的指针",但是它没有明确指定它是否适用于此类的递归派生指针.在其他情况下,在标准中提到了指针的特殊属性时,它没有明确指定或提及递归指针派生,这意味着类型派生"适用或不适用,但未明确提及. /p>

尽管引用类型时使用的所有指针"一词仅使用两次(对于结构和联合类型),而不是更明确的短语:指向"的指针在整个标准中都使用,我们无法断定它是否适用于此类指针的递归派生.

解决方案

背景

该标准隐含地要求所有指向结构类型(完整,不完整,兼容和不兼容)的指针具有相同的表示和对齐要求,这种假设始于C89,比标准明确要求它早很多年.其背后的原因是不完整类型在单独的翻译单元中的兼容性,尽管根据C标准委员会的说法,最初的意图是允许不完整类型与其完整的变体兼容,但该标准的实际用语并未描述它.在C89的第二次技术更正中对此进行了修改,因此使原始假设变得具体.


兼容性和不完整类型

在阅读与兼容性和不完整类型有关的指南时,多亏了Matt McNabb,我们才能进一步了解原始的C89假设.

对象和不完整类型的指针派生

C99/C11 §6.2.5 p1:

类型分为对象类型,函数类型和不完整类型.

C99/C11 §6.2.5 p20:

指针类型可以从函数类型,对象类型或不完整类型(称为引用类型)派生.

C99/C11 §6.2.5 p22:

未知内容的结构或联合类型是不完整的类型.对于该类型的所有声明,通过在稍后在同一作用域中声明相同的结构或联合标签及其定义内容来完成此操作.

这意味着指针可以从对象类型和不完整类型中派生.尽管未指定不完整​​的类型并不需要完成;过去,委员会对此事做出了回应,并表示,没有禁令就足够了,也没有必要发表肯定的声明.

以下指向不完整的"struct never_completed"结构的指针永远不会完成:

int main(void)
{
    struct never_completed *p;
    p = malloc(1024);
}

[完整的代码示例]

兼容类型的单独翻译单元

C99/C11 §6.7.2.3 p4:

所有具有相同范围并使用相同标记的结构,联合或枚举类型的声明都声明相同的类型.

C99/C11 §6.2.7 p1:

如果两个类型相同,则它们具有兼容类型.如果两个结构类型的标签是同一标签,则在两个单独的转换单元中声明的两种结构类型将兼容. [修饰语] [...]

此段具有重要意义,请允许我对其进行总结:如果在两个单独的翻译单元中声明的两种结构类型使用相同的标记,则它们是兼容的.如果他们两个都完成了,则他们的成员必须相同(根据指定的准则).

指针的兼容性

C99 §6.7.5.1 p2/C11 §6.7.6.1 p2:

要使两个指针类型兼容,则两者必须具有相同的限定条件,并且都应是指向兼容类型的指针.

如果标准要求在指定条件下两个结构在不同的翻译单元中是兼容的(无论是不完整的还是完整的),则意味着从这些结构派生的指针也要兼容.

C99/C11 §6.2.5 p20:

可以从对象,函数和不完整类型中构造任意数量的派生类型

这些构造派生类型的方法可以递归应用.

由于指针派生是递归的,因此使得从指针派生到兼容结构类型的指针彼此兼容.

兼容类型的表示形式

C99 §6.2.5 p27/C11 §6.2.5 p28:

兼容类型的合格或不合格版本的指针应具有相同的表示和对齐要求.

C99/C11 §6.3 p2:

将操作数值转换为兼容类型不会更改值或表示形式.

C99/C11 §6.2.5 p26:

一个类型的合格或不合格版本是属于同一类型类别并具有相同表示和对齐要求的不同类型.

这意味着由于分离的翻译单元可能具有兼容的类型,因此一致的实现对从不完整或完整的结构类型派生的指针的表示和对齐要求没有明确的判断.具有相同的表示和对齐要求,并且需要对相同结构类型的不完整或完整变体应用相同的不同判断.

以下指向 incomplete 'struct complete_incomplete'结构的指针:

struct complete_incomplete ** p;

与以下指向 complete 'struct complete_incomplete'结构的指针兼容,并且具有相同的表示形式和对齐要求:

struct complete_incomplete {int i; } ** p;


与C89相关

如果我们想知道有关C89的前提,则质疑Jun 93'的缺陷报告#059:

两个部分均未明确要求最终必须完成不完整的类型,也未明确允许不完整的类型对于整个编译单元保持不完整.由于此功能对于声明真正的不透明数据类型非常重要,因此值得澄清.

考虑在不同的编译单元中定义和实现的相互引用结构,使不透明数据类型的思想自然成为不完整数据类型的扩展.

委员会的答复是:

委员会在起草C标准时考虑并认可了不透明的数据类型.


兼容性与互换性

我们已经介绍了有关结构类型的指针的递归指针派生的表示形式和对齐要求的方面,现在我们面临的问题是一个非规范性脚注可互换性":

C99 TC3 §6.2.5 p27 Footnote 39/C11 §6.2.5 p28 Footnote 48:

相同的表示形式和对齐要求旨在暗示与函数的参数,函数的返回值以及并集的成员的互换性.

该标准表示注释,脚注和示例是非规范性的,并且仅供参考".

C99 FOREWORD p6/C11 FOREWORD p8:

[...]此前言,简介,注释,脚注和示例也仅供参考.

不幸的是,这个令人困惑的脚注从未改变,因为充其量-该脚注充其量是专门针对引用该脚注的直接类型,因此如果表示和对齐要求"的属性没有这些特定的类型,可以很容易地将其解释为共享表示和对齐方式的所有类型的一般规则.如果要在没有特定类型的上下文的情况下解释脚注,那么很明显,即使无需辩论术语可互换"的解释,该标准的规范性文字也没有暗示这一点.

指向结构类型的指针的兼容性

C99/C11 §6.7.2.3 p4:

所有具有相同范围并使用相同标记的结构,联合或枚举类型的声明都声明相同的类型.

C99/C11 §6.2.7 p1:

如果两个类型相同,则两个类型具有兼容类型.

C99 §6.7.5.1 p2/C11 §6.7.6.1 p2:

要使两个指针类型兼容,则两者必须具有相同的限定条件,并且都应是指向兼容类型的指针.

这得出一个显而易见的结论,不同的结构类型确实是不同的类型,并且由于它们不同而互不兼容.因此,两个指向两个不同且不兼容的类型的指针也都是不兼容的,而不管它们的表示形式和对齐要求如何.

有效类型

C99/C11 §6.5 p7:

只能通过具有以下类型之一的左值表达式访问对象的存储值:

与对象的有效类型兼容的类型

C99/C11 §6.5 p6:

用于访问其存储值的对象的有效类型是该对象的声明类型(如果有).

不兼容的指针不能作为函数的参数或函数的返回值互换".隐式转换和指定的特殊情况是例外,并且这些类型不属于任何此类例外.即使我们决定为上述可互换性"添加一个不切实际的要求,并说需要使用显式转换才能适用,但是访问具有不兼容有效类型的对象的存储值会破坏有效类型规则.为了使其成为现实,我们需要该标准当前没有的新属性.因此,仅仅拥有相同的表示和对齐要求并可以转换是远远不够的.

这使我们可以作为工会成员"互换使用,尽管它们确实可以互换为工会成员,但这没有特殊意义.

官方解释

1..第一个官方"解释属于C标准委员会的成员.他对本意是暗示可互换性"的解释是,它实际上并不意味着存在这种可互换性,但实际上对此提出了建议.

尽管我希望它成为现实,但我不会考虑从非规范性脚注中提出建议的实现,更不用说一个不合理的模糊脚注,同时又与规范性准则相抵触的实现.执行.显然,这使得利用并依赖于这种建议"的程序成为非严格遵循的程序.

2..第二种正式"解释属于C标准委员会的成员/贡献者,根据他的解释,脚注并未提出建议,并且因为标准并不意味着它—他认为这是标准的缺陷.他甚至提出了修改有效类型规则以解决此问题的建议.

3..第三种官方"解释来自93年12月93日的缺陷报告#070.在C89的上下文中,已经问过一个程序,该程序是否传递"unsigned int"类型(期望类型为"int")作为带有非原型声明符的函数的参数,以引入未定义的行为.

在C89中有一个完全相同的脚注,它的隐含可互换性与附加到函数的参数相同,

C89 §3.1.2.5 p2:

有符号整数类型的非负值范围是相应的无符号整数类型的子范围,并且每种类型中相同值的表示形式相同.

委员会回应说,他们鼓励实施者允许这种互换性起作用,但是由于这不是一项要求,因此使该计划成为非严格符合要求的计划.


以下代码示例不完全符合. & s1"和结构通用**"共享相同的表示形式和对齐要求,但仍然不兼容.根据有效类型规则,我们正在使用不兼容的有效类型(指向结构通用"的指针)访问对象"s1"的存储值,而其声明的类型(即有效类型)则是结构s1"的指针'.为了克服此限制,我们可以将指针用作联合的成员,但是这种约定损害了通用的目标.

int allocate_struct(void    *p,
                    size_t  s)
{
    struct generic **p2 = p;
    if ((*p2 = malloc(s)) == NULL)
        return -1;

    return 0;
}

int main(void)
{
    struct s1 { int i; } *s1;

    if (allocate_struct(&s1, sizeof *s1) != 0)
        return EXIT_FAILURE;
}

[完整的代码示例]


以下代码示例严格符合,以克服有效类型和泛型这两个问题,我们利用以下优势:1.指向void的指针,2.表示形式和对齐要求所有指向结构的指针,以及3.使用memcpy复制表示形式时,一般"访问指针的字节表示形式,而不会影响其有效类型.

int allocate_struct(void    *pv,
                    size_t  s)
{
    struct generic *pgs;

    if ((pgs = malloc(s)) == NULL)
        return -1;

    memcpy(pv, &pgs, sizeof pgs);
    return 0;
}

int main(void)
{
    struct s1 { int i; } *s1;

    if (allocate_struct(&s1, sizeof *s1) != 0)
        return EXIT_FAILURE;
}

[完整的代码示例]


结论

结论是,对于所有递归派生的指向结构类型的指针,无论它们是不完整的还是完整的,以及它们是兼容还是不兼容,一致的实现都必须分别具有相同的表示和对齐要求.尽管类型是兼容还是不兼容很重要,但是由于仅是兼容类型的可能性,它们必须共享表示和对齐的基本属性.如果我们可以直接访问共享表示和对齐方式的指针,那将是首选,但是不幸的是,当前的有效类型规则并不需要它.

The Question

The question of whether all pointers derived from pointers to structure types are the same, is not easy to answer. I find it to be a significant question for the following two primary reasons.

A. The lack of a pointer to pointer to 'any' incomplete or object type, imposes a limitation on convenient function interfaces, such as:

int allocate(ANY_TYPE  **p,
             size_t    s);

int main(void)
{
    int *p;
    int r = allocate(&p, sizeof *p);
}

[Complete code sample]

The existing pointer to 'any' incomplete or object type is explicitly described as:

C99 / C11 §6.3.2.3 p1:

A pointer to void may be converted to or from a pointer to any incomplete or object type. [...]

A pointer derived from the existing pointer to 'any' incomplete or object type, pointer to pointer to void, is strictly a pointer to pointer to void, and is not required to be convertible with a pointer derived from a pointer to 'any' incomplete or object type.


B. It is not uncommon for programmers to utilize conventions based on assumptions that are not required, related to the generalization of pointers, knowingly or unknowingly, while depending on their experience with their specific implementations. Assumptions such as being convertible, being representable as integers, or sharing a common property: object size, representation, or alignment.


The words of the standard

According to C99 §6.2.5 p27 / C11 §6.2.5 p28:

[...] All pointers to structure types shall have the same representation and alignment requirements as each other. [...]

Followed by C99 TC3 Footnote 39 / C11 Footnote 48:

The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and members of unions.

Although the standard doesn't say: "A pointer to a structure type" and the following words have been chosen: "All pointers to structure types", it doesn't explicitly specify whether it applies to a recursive derivation of such pointers. In other occasions where special properties of pointers are mentioned in the standard, it doesn't explicitly specify or mention recursive pointer derivation, which means that either the 'type derivation' applies, or it doesn't- but it's not explicitly mentioned.

And although the phrasing "All pointers to" while referring to types is used only twice, (for structure and union types), as opposed to the more explicit phrasing: "A pointer to" which is used throughout the standard, we can't conclude whether it applies to a recursive derivation of such pointers.

解决方案

Background

The assumption that the standard implicitly requires all pointers to structure types, (complete, incomplete, compatible and incompatible), to have the same representation and alignment requirements, began at C89- many years before the standard required it explicitly. The reasoning behind it was the compatibility of incomplete types in separate translation units, and although according to the C standards committee, the original intent was to allow the compatibility of an incomplete type with its completed variation, the actual words of the standard did not describe it. This has been amended in the second Technical corrigendum to C89, and therefore made the original assumption concrete.


Compatibility and Incomplete Types

While reading the guidelines related to compatibility and incomplete types, thanks to Matt McNabb, we find further insight of the original C89 assumption.

Pointer derivation of object and incomplete types

C99 / C11 §6.2.5 p1:

Types are partitioned into object types, function types, and incomplete types.

C99 / C11 §6.2.5 p20:

A pointer type may be derived from a function type, an object type, or an incomplete type, called the referenced type.

C99 / C11 §6.2.5 p22:

A structure or union type of unknown content is an incomplete type. It is completed, for all declarations of that type, by declaring the same structure or union tag with its defining content later in the same scope.

Which means that pointers may be derived from both object types and incomplete types. Although it isn't specified that incomplete types are not required to be completed; in the past the committee responded on this matter, and stated that the lack of a prohibition is sufficient and there's no need for a positive statement.

The following pointer to pointer to incomplete 'struct never_completed', is never completed:

int main(void)
{
    struct never_completed *p;
    p = malloc(1024);
}

[Complete code sample]

Compatible types of separate translation units

C99 / C11 §6.7.2.3 p4:

All declarations of structure, union or enumerated types that have the same scope and use the same tag declare the same type.

C99 / C11 §6.2.7 p1:

Two types have compatible type if their types are the same. Two structure types declared in separate translation units are compatible if their tags (are) the same tag. [trimmed quote] [...]

This paragraph has a great significance, allow me to summarize it: two structure types declared in separate translation units are compatible if they use the same tag. If both of them are completed- their members have to be the same (according to the specified guidelines).

Compatibility of pointers

C99 §6.7.5.1 p2 / C11 §6.7.6.1 p2:

For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.

If the standard mandates that two structures under specified conditions, are to be compatible in separate translation units whether being incomplete or complete, it means that the pointers derived from these structures are compatible just as well.

C99 / C11 §6.2.5 p20:

Any number of derived types can be constructed from the object, function, and incomplete types

These methods of constructing derived types can be applied recursively.

And due to the fact that pointer derivation is recursive, it makes pointers derived from pointers to compatible structure types, to be compatible with each other.

Representation of compatible types

C99 §6.2.5 p27 / C11 §6.2.5 p28:

pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements.

C99 / C11 §6.3 p2:

Conversion of an operand value to a compatible type causes no change to the value or the representation.

C99 / C11 §6.2.5 p26:

The qualified or unqualified versions of a type are distinct types that belong to the same type category and have the same representation and alignment requirements.

This means that a conforming implementation can't have a distinct judgement concerning the representation and alignment requirements of pointers derived from incomplete or complete structure types, due to the possibility that a separate translation unit might have a compatible type, which will have to share the same representation and alignment requirements, and it is required to apply the same distinct judgement with either an incomplete or a complete variation of the same structure type.

The following pointer to pointer to incomplete 'struct complete_incomplete':

struct complete_incomplete **p;

Is compatible and shares the same representation and alignment requirements as the following pointer to pointer to complete 'struct complete_incomplete':

struct complete_incomplete { int i; } **p;


C89 related

If we wonder about the premise concerning C89, defect report #059 of Jun 93' questioned:

Both sections do not explicitly require that an incomplete type eventually must be completed, nor do they explicitly allow incomplete types to remain incomplete for the whole compilation unit. Since this feature is of importance for the declaration of true opaque data types, it deserves clarification.

Considering mutual referential structures defined and implemented in different compilation units makes the idea of an opaque data type a natural extension of an incomplete data type.

The response of the committee was:

Opaque data types were considered, and endorsed, by the Committee when drafting the C Standard.


Compatibility versus Interchangeability

We have covered the aspect concerning the representation and alignment requirements of recursive pointer derivation of pointers to structure types, now we are facing a matter that a non-normative footnote mentioned, 'interchangeability':

C99 TC3 §6.2.5 p27 Footnote 39 / C11 §6.2.5 p28 Footnote 48:

The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and members of unions.

The standard says that the notes, footnotes, and examples are non-normative and are "for information only".

C99 FOREWORD p6 / C11 FOREWORD p8:

[...] this foreword, the introduction, notes, footnotes, and examples are also for information only.

It's unfortunate that this confusing footnote was never changed, because at best- the footnote is specifically about the direct types referring to it, so phrasing the footnote as-if the properties of "representation and alignment requirements" are without the context of these specific types, makes it easy to interpret as being a general rule for all types that share a representation and alignment. If the footnote is to be interpreted without the context of specific types, then it's obvious that the normative text of the standard doesn't imply it, even without the need to debate the interpretation of the term 'interchangeable'.

Compatibility of pointers to structure types

C99 / C11 §6.7.2.3 p4:

All declarations of structure, union or enumerated types that have the same scope and use the same tag declare the same type.

C99 / C11 §6.2.7 p1:

Two types have compatible type if their types are the same.

C99 §6.7.5.1 p2 / C11 §6.7.6.1 p2:

For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.

This states the obvious conclusion, different structure types are indeed different types, and because they are different they are incompatible. Therefore, two pointers to two different and incompatible types, are incompatible just as well, regardless of their representation and alignment requirements.

Effective types

C99 / C11 §6.5 p7:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

a type compatible with the effective type of the object

C99 / C11 §6.5 p6:

The effective type of an object for an access to its stored value is the declared type of the object, if any.

Incompatible pointers are not 'interchangeable' as arguments to functions, nor as return values from functions. Implicit conversions and specified special cases are the exceptions, and these types are not part of any such exception. Even if we decide to add an unrealistic requirement for said 'interchangeability', and say that an explicit conversion is required to make it applicable, then accessing the stored value of an object with an incompatible effective type breaks the effective types rules. For making it a reality we need a new property that currently the standard doesn't have. Therefore sharing the same representation and alignment requirements, and being convertible, is simply not enough.

This leaves us with being interchangeable 'as members of unions', and although they are indeed interchangeable as members of union- it bears no special significance.

Official interpretations

1. The first 'official' interpretation belongs to a member of the C standards committee. His interpretation for: "are meant to imply interchangeability", is that it doesn't actually imply that such an interchangeability exists, but actually makes a suggestion for it.

As much as I would like it to become a reality, I wouldn't consider an implementation that took a suggestion from a non-normative footnote, not to mention an unreasonably vague footnote, while contradicting normative guidelines- to be a conforming implementation. This obviously renders a program that utilizes and depends on such a 'suggestion', to be a non-strictly conforming one.

2. The second 'official' interpretation belongs to a member/contributor to the C standards committee, by his interpretation the footnote doesn't introduce a suggestion, and because the (normative) text of standard doesn't imply it- he considers it to be a defect in the standard. He even made a suggestion to change the effective types rules for addressing this matter.

3. The third 'official' interpretation is from defect report #070 of Dec 93`. It has been asked, within the context of C89, whether a program that passes an 'unsigned int' type, where the type 'int' is expected, as an argument to a function with a non-prototype declarator, to introduce undefined behavior.

In C89 there's the very same footnote, with the same implied interchangeability as arguments to functions, attached to:

C89 §3.1.2.5 p2:

The range of nonnegative values of a signed integer type is a subrange of the corresponding unsigned integer type, and the representation of the same value in each type is the same.

The committee responded that they encourage implementors to allow this interchangeability to work, but since it's not a requirement, it renders the program to be a non-strictly conforming one.


The following code sample is not strictly conforming. '&s1' and 'struct generic **' are sharing the same representation and alignment requirements, but nevertheless they are incompatible. According to the effective types rules, we are accessing the stored value of the object 's1' with an incompatible effective type, a pointer to 'struct generic', while its declared type, and therefore effective type, is a pointer to 'struct s1'. To overcome this limitation we could've used the pointers as members of a union, but this convention damages the goal of being generic.

int allocate_struct(void    *p,
                    size_t  s)
{
    struct generic **p2 = p;
    if ((*p2 = malloc(s)) == NULL)
        return -1;

    return 0;
}

int main(void)
{
    struct s1 { int i; } *s1;

    if (allocate_struct(&s1, sizeof *s1) != 0)
        return EXIT_FAILURE;
}

[Complete code sample]


The following code sample is strictly conforming, to overcome both issues of effective types and being generic, we're taking advantage of: 1. a pointer to void, 2. the representation and alignment requirements of all pointers to structs, and 3. accessing the pointer's byte representation 'generically', while using memcpy to copy the representation, without affecting its effective type.

int allocate_struct(void    *pv,
                    size_t  s)
{
    struct generic *pgs;

    if ((pgs = malloc(s)) == NULL)
        return -1;

    memcpy(pv, &pgs, sizeof pgs);
    return 0;
}

int main(void)
{
    struct s1 { int i; } *s1;

    if (allocate_struct(&s1, sizeof *s1) != 0)
        return EXIT_FAILURE;
}

[Complete code sample]


The Conclusion

The conclusion is that a conforming implementation must have the same representation and alignment requirements, respectively, for all recursively derived pointers to structure types, whether they are incomplete or complete, and whether they are compatible or incompatible. Although whether the types are compatible or incompatible is significant, but due to the mere possibility of a compatible type, they must share the fundamental properties of representation and alignment. It would've been preferred if we could access pointers that share representation and alignment directly, but unfortunately the current effective types rules do not require it.

这篇关于从结构类型的指针派生的所有指针是否都相同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆