ISO/IEC 9899:2011 条款6.7.2——类型说明符

6.7.2 类型说明符


语法

1、type-specifier:

        void

        char

        short

        int

        long

        float

        double

        signed

        unsigned

        _Bool

        _Complex

        atomic_type-specifier

        struct-or-union-specifier

        enum-specifier

        typedef-name

约束

2、在每个声明中的声明说明符中,以及在每个结构体声明与类型名中的specifier-qualifier列表中的声明说明符中应该至少给出一个类型说明符。类型说明符的每个列表应该是以下多重集的其中之一(用逗号分隔,当每个项有多个多重集的时候);类型说明符可以以任一次序出现,可能与其它声明说明符相互混合。

——void

——char

——signed char

——unsigned char

——shortsigned shortshort int,或signed short int

——unsigned short,或unsigned short int

——intsigned,或signed int

——unsigned,或unsigned int

——longsigned longlong int,或signed long int

——unsigned long,或unsigned long int

——long longsigned long longlong long int,或signed long long int

——unsigned long long,或unsigned long long int

——float

——double

——long double

——_Bool

——float _Complex

——double _Complex

——long double _Complex

——原子类型说明符

——struct或union说明符

——enum说明符

——typedef名

3、如果实现不支持复数类型,那么类型说明符_Complex不应该被使用(见6.10.8.3)。

语义

4、用于结构体、联合体、枚举、以及原子类型的说明符在6.7.2.1到6.7.2.4中讨论。typedef名的声明在6.7.8中讨论。其它类型的特征在6.2.5中讨论。

5、每个逗号分隔的多重集指派了相同的类型,除了对于位域,int说明符指派的是与signed int相同的类型还是与unsigned int相同的类型是由实现定义的。


6.7.2.1 结构体与联合体说明符

语法

1、struct-or-union-specifier:

        struct-or-union    identifieropt    {    struct-declaration-list    }

        struct-or-union    identifier

    struct-or-union:

        struct

        union

    struct-declaration-list:

        struct-declaration

        struct-declaration-list    struct-declaration

    struct-declaration:

        specifier-qualifier-list        struct-declarator-listopt        ;

        static_assert-declaration

    specifier-qualifier-list:

        struct-declarator

        struct-declarator-list    ,    struct-declarator

    struct-declarator:

        declarator
        declaratoropt    :    constant-expression

约束

2、没有声明一个匿名结构体也没有声明一个匿名联合体的一个struct-declaration应该包含一个struct-declarator-list。

3、一个结构体或联合体不应该包含一个带有不完整类型或函数类型(因此,一个结构体不应该包含其自身的一个实例,但是可以包含一个指向其自身实例的指针),除了具有多个命名成员的一个结构体的最后一个成员可以含有不完整的数组类型;这么一个结构体(以及任一联合体,可能递归地包含这么一个结构体的一个成员)不应该是一个结构体的一个成员也不应该是一个数组的一个元素。

4、指定了一个位域宽度的表达式应该是一个带有非负数值的整数常量表达式,该值不超过该类型的一个对象的宽度,通过分号与缺省表达式来指定。[注:当一个_Bool对象中的比特的成员至少是CHAR_BIT时,一个_Bool的宽度(符号位以及值位的个数)可以被调整为1比特。]如果值为零,那么该声明应该不具有声明符。[译者注:如以下代码所示:

struct MyStruct
{
    _Bool a: 1;    // OK
    _Bool : 0;     // OK,声明了匿名成员
    //_Bool b: 0;  ERROR: 命名成员的宽度不能为零
};

5、一个位域应该具有一个类型,它是一个_Boolsigned intunsigned int,或某些其它实现定义的类型的的一个限定或非限定版本。原子类型是否被允许是由实现定义的。

语义

6、正如在6.2.5中所讨论的,一个结构体是一个由成员序列所构成的一个类型,其存储空间以一个排好次序的序列进行分配。而一个联合体是由成员序列所组成的一个类型,这些成员的存储相互叠交。

7、结构体与联合体说明符具有相同形式。关键字structunion分别指示了正被指定的类型。

8、一个struct-or-union-specifier中存在一个struct-declaration-list,在一个翻译单元中声明了一个新的类型,struct-declaration-list是对该结构体或联合体的成员的声明序列。如果struct-declaration-list并不包含任何命名的成员,那么无论是直接的还是通过一个匿名结构体或匿名联合体,行为都是未定义的。此类型是不完整的,直到立即跟在 } 之后终结此列表,从而变为完整的。

9、一个结构体或联合体的一个成员可以具有任一不完整的对象类型,只要不是可变修改的类型。[注:一个结构体或联合体不能含有一个可变修改类型的成员,因为成员名不是普通的标识符,在6.2.3中定义。]此外,一个成员可以被声明为由一组指定个数的比特组成(包括一个符号位,如果存在的话)。这么一个成员被称为位域;[注:单目 &(地址)操作符不能被应用于一个位域对象;从而,没有志向位域对象的指针,也没有位域数组。]其宽度前面用一个冒号分隔。

10、一个位域被解释为由指定个数比特所组成的,具有一个带符号或无符号整数类型。[注:正如在上述6.7.2中所指定的,如果实际所使用的类型说明符是int或是一个定义为int的typedef名,那么该位域是带符号还是无符号的是由实现定义的。]如果值0或1被存储在一个非零宽度的类型为_Bool的位域中,那么位域的值与所存储的值比较起来应该相等;一个_Bool位域具有一个_Bool的语义。

11、一个实现可以分配任一足够大的可寻址的存储单元,以存放一个位域。如果仍然还有足够的空间,在一个结构体中,一个立即跟在另一个位域之后的位域应该被打包为同一单元的临近比特。如果仍然具有充足的空间,那么一个无法适应的位域是放在下一个单元还是与邻近单元叠交是由实现定义的。在一个单元内的位域分配的次序(高序到低序还是低序到高序)是由实现定义的。可寻址存储单元的对齐是未指定的。

12、一个不含声明符,而只有一个冒号与一个宽度的位域声明,指示了一个匿名位域。[注:一个匿名位域结构体成员,对于填充以遵循外部所必须满足的布局要求而言是有用的。]作为一个特殊情况,具有0宽度的一个位域成员指明了没有进一步的位域被打包在之前位域所在的单元。

13、一个匿名成员,其类型说明符是一个没有结构体标签的结构体说明符,被称为一个匿名结构体;一个匿名成员,其类型说明符是一个没有联合体标签的联合体说明符,被称为一个匿名联合体。一个匿名结构体与联合体的成员被认作为包含该结构体或联合体的成员。这会递归应用,如果所包含的结构体或联合体也是匿名的。

14、一个结构体或联合体对象的每个非位域成员以一个实现定义的方式来对齐,适用于其类型。

15、在一个结构体对象内,非位域成员与位域所驻留的存储单元具有与它们所声明的次序递增的地址。一个指向结构体对象的指针被适当地转换为指向其初始成员的指针(或者,如果那个成员是一个位域,则指向它所驻留的存储单元。),并且反过来也一样。在一个结构体对象内可以有匿名填充,但不是在其起始处。

16、一个联合体的大小要足够能包含其尺寸最大的一个成员。在任一时刻,在一个联合体对象中最多要能存放其中一个成员的值。一个指向联合体对象的指针可以被适当地转换为其每个成员的指针(或者,如果一个成员是一个位域,则指向它所驻留的存储单元),并且反过来也如此。

17、在一个结构体或联合体末尾可以有匿名填充。

18、作为一个特殊例子,具有多个命名成员的一个结构体的最后一个元素可以具有一个不完整的数组类型;这被称为一个灵活的数组成员。在大部分情况下,灵活的数组成员被忽略。特别地,结构体的大小就好比灵活的数组成员被忽略一样,除了它可以比忽略所暗示的具有更多的尾随填充。然而,当一个 .(或->)操作符具有一个带有灵活数组成员的结构体(指向它的一个指针)作为左操作数时,并且右操作数命名了那个成员,那么其行为就好比那个成员用最长的数组(具有相同元素类型)来替换,该数组不会使得该结构体比正访问的对象更长;数组的偏移应该仍然为灵活数组成员的偏移,即使这与所替换数组的的偏移会有所不同。如果该数组没有元素,那么其行为就好比它含有一个元素,但该行为是未定义的,如果任一要对 那个元素进行访问或是生成超过它的一个指针。

19、例1 以下描述了匿名结构体与联合体:

struct v {
    union {    // 匿名联合体
        struct { int i, j; };    // 匿名结构体
        struct { long k, l; } w;
    };
    int m;
} v1;

v1.i = 2;    // 有效
v1.k = 3;    // 无效:内部结构体不是匿名的
v1.w.k = 5;  // 有效

20、例2 在以下声明之后:

struct s { int n; double d[]; };

结构体struct s具有一个灵活的数组成员d。对其使用的一种典型的方式为:

int m = /* 某个值 */;
struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));

并假定对malloc的调用成功,对于大多数目的,由p所指向的对象行为表现就好比p已经被声明为:

struct { int n; double d[m]; } *p;

(会有一些情况来打破这种等价;特别地,成员d的偏移可能不相同。)

21、紧接着上述声明:

struct s t1 = { 0 };             // 有效
struct s t2 = { 1, { 4.2 } };    // 无效
t1.n = 4;         // 有效
t1.d[0] = 4.2;    // 可能是未定义行为

t2的初始化是无效的(且违反了约束),因为struct s被对待为就好比它并不包含成员d。对t1.d[0]的赋值可能是未定义的行为,但可能是

sizeof (struct s) >= offsetof(struct s, d) + sizeof(double)

在这种情况下,赋值会是合法的。然而,这不能以严格遵循标准的代码而出现。

22、在进一步声明之后:

struct ss { int n; };

表达式:

sizeof (struct s) >= sizeof (struct ss)

sizeof (struct s) >= offsetof(struct s, d)

总是等于1。

23、如果sizeof (double) 是8,那么在以下代码被执行之后:

struct s *s1;
struct s *s2;
s1 = malloc(sizeof (struct s) + 64);
s2 = malloc(sizeof (struct s) + 46);

并假定对malloc的调用成功,对于大多数目的,被是s1s2所指向的对象的行为,就好比标识符已经被声明为:

struct { int n; double d[8]; } *s1;
struct { int n; double d[5]; } *s2;

译者注:这里实现对double的长度定义为64位,即8字节。那么对于s2 = malloc(sizeof (struct s) + 46; 46个字节正好介于d[5](40个字节)和d[6](48字节)之间,因此取不大于它的最大整数值,即40个字节就完全能满足存储空间需求,所以对于s2就好比是定义了struct { int n; double d[5]; } *s2;]

24、跟在进一步成功赋值之后:

s1 = malloc(sizeof (struct s) + 10);
s2 = malloc(sizeof (struct s) + 6);

它们行为就好比声明了:

struct { int n; double d[1]; } *s1, *s2;

并且

double *dp;
dp = &(s1->d[0]);    // 有效
*dp = 42;    // 有效
dp = &(s2->d[0]);    // 有效
*dp = 42;    // 未定义行为

25、赋值语句: *s1 = *s2;

仅仅拷贝成员n;如果任意一个数组元素在第一个sizeof (struct s)结构体字节内,那么它们可能被拷贝,或仅仅用中间值覆盖。

26、例3 因为匿名结构体与联合体的成员被看作为包含在那结构体或联合体内的成员, 因而在以下例子中的struct s具有多个命名成员,并从而使用一个灵活的数组的成员是有效的:

struct s {
    struct { int i; };
    int a[];
};


6.7.2.2 枚举说明符

语法

1、enum-specifier:

        enum    identifieropt    {    enumerator-list    }

        enum    identifieropt

        enum    identifier

    enumerator-list:

        enumerator

        enumerator-list    ,    enumerator

    enumerator:

        enumerator-constant

        enumerator-constant    =    constant-expression

约束

2、定义一个枚举常量值的表达式应该是一个整数常量表达式,它具有一个可表示为int的值。

语义

3、一个枚举符列表中的标识符被定义为具有int类型的常量,并且可以出现在这所允许出现的任何地方。[注:从而,在同一作用域所声明的枚举常量的标识符应该相互之间有所区别,并且与在普通声明符中所声明的标识符也有所区别。]一个带有 = 的枚举符将其枚举常量定义为常量表达式的值。如果第一个枚举符不具有 =,那么其枚举常量的值为0。每个后续不带有 = 的枚举符将其枚举常量定义为由对其之前一个枚举常量的值加1后所获得的常量表达式的值。(具有 = 的枚举符的使用可以产生在同一枚举中具有重复其它值的枚举常量。)一个枚举的枚举符被称为其成员。

4、每个枚举类型应该与char、一个带符号整数类型,或是一个无符号整数类型相兼容。类型的选择是实现定义的,[注:一个实现可以延迟选择哪个整数类型,直到所有枚举常量都已被看见。]但应该能够表示枚举的所有成员的值。枚举类型是一个不完整类型,直到立即在  } 之后结束枚举符声明列表,在此之后完成。

5、例 以下代码片段:

enum hue { chartreuse, burgundy, claret=20, winedark };
enum hue col, *cp;
col = claret;
cp = &col;
if (*cp != burgundy)
    /* ... */

使得hue作为一个枚举的标签,然后将col声明为具有此类型的一个对象,以及将cp声明为一个指向此类型的一个对象的指针。枚举值在此集合中为{ 0, 1, 20, 21 }。


6.7.2.3 标签[译者注:Tags]

约束

1、一个特定类型应该最多具有一个其定义的内容。

2、在使用相同标签声明了相同类型的两个声明的地方,它们应该都使用选择了一样的structunion,或enum

3、形式 enum identifier 的一个类型说明符,没有枚举符列表,应该仅出现在它所指定的类型被完成之后。

语义

4、所有对结构体、联合或枚举类型的声明,它们具有相同作用域并使用相同标签,则声明了相同的类型。不管是否有一个标签还是该类型的其它声明在同一翻译单元中,该类型是不完整的。[注:一个不完整类型只能在该类型的一个对象的大小不被需要的时候才能被使用。所谓的不被需要,比如说,当一个typedef名被声明为用于一个结构体或联合体的说明符时,或是当一个指向一个返回一个结构体或联合体的函数的指针正被声明时。(见6.2.5中不完整类型。)此指定在这么一个函数被调用或定义之前必须是完整的。],直到立即在定义该内容的列表的封闭花括号之后,并在此之后完成。

5、在不同作用域或使用不同标签的对结构体、联合体或枚举类型的声明,声明了两个不同的类型。一个不包含一个标签的结构体、联合体或枚举类型声明了一个不同的类型。

6、以下形式的一个类型说明符

    struct-or-union    identifieropt    {    struct-declaration-list    }

    enum    identifieropt    {    enumerator-list    }

    enum    identifieropt    {    enumerator-list     ,    }

声明了一个结构体、联合体或枚举类型。该列表定义了结构体内容联合体内容,或枚举内容。如果提供了一个标识符[注:如果没有标识符,那么该类型可以在翻译单元内,仅通过作为其一部分的声明所引用。当然,当此声明为一个typedef名时,子序列声明可以利用typedef名来声明具有所指定的结构体、联合体、或枚举类型的对象。],那么类型说明符也将该标识符声明为该类型的标签。

7、以下形式的一个声明

    struct-or-union    identifier    ;

指定了一个结构体或联合体类型,并将该标识符声明为该类型的一个标签。[注:一个用enum的类似构造并不存在。]

8、如果以下形式的类型说明符

    struct-or-union    identifier

发生,而不作为上述形式之一的一部分,并且没有其它该标识符的声明作为一个标签可见,那么它声明了一个不完整结构体或联合体类型,并将该标识符声明为该类型的标签。[注:一个用enum的类似构造并不存在。]

9、如果以下形式的一个类型说明符

    struct-or-union    identifier

    enum    identifier

发生,但不作为上述形式之一的一部分,并且对该标识符声明为一个标签可见,那么它指定了与其它声明相同的类型,且并不重新声明该标签。

10、例1 这个机制允许一个自引用结构体的声明。

struct tnode {
    int count;
    struct tnode *left, *right;
};

指定了包含一个整数与指向相同类型对象的两个指针的一个结构体。一旦给定了这个声明,那么该声明

struct tnode s, *p;

s声明为该给定类型的一个对象,以及将sp声明为指向该给定类型的一个对象的一个指针。随着这些声明,表达式 sp->left 引用了sp所指向的对象的左struct tnode的指针;s.right->count 指派了从s指向的右struct tnode的成员count

11、以下可替换的公式化语句使用了typedef机制:

typedef struct tnode TNODE;
struct tnode {
    int count;
    TNODE *left, *right;
};
TNODE s, *sp;

12、例12 为了描述对之前声明的一个标签的使用,以指定一对相互引用的结构体,以下声明

struct s1 { struct s2 *s2p;    /* ... */ };    // D1
struct s2 { struct s1 *s1p;    /* ... */ };    // D2

指定了一对含有相互指向的指针的结构体。注意,然而,如果s2已经被声明为在一个封闭作用域中的一个标签,那么对D1的声明将引用它,而不是引用声明在D2中的s2。为了消除这个上下文敏感性,声明 struct s2; 可以插入到D1之前。这在内部作用域声明了一个新的标签s2;声明D2然后完成对新类型的指定。


6.7.2.4 原子类型说明符

语法

1、atomic-type-specifier:

        _Atomic (    type-name    )

约束

2、一个原子类型说明符不应该被使用,如果实现不支持原子类型(见6.10.8.3)。

3、一个原子类型说明符中的类型名不应该引用一个数组类型、一个函数类型、一个原子类型,以及一个限定类型。

语义

4、与原子类型相关联的属性仅对作为左值的表达式有意义。如果_Atomic关键字后面立即跟着一个左圆括号,那么它被解释为一个类型说明符(带有一个类型名),而不是一个类型限定符。

原文地址:https://www.cnblogs.com/zenny-chen/p/4881170.html