一 概述
二 类型
三 语句
四 函数
五 数据
六 内存
七 代码
附录
5. 结构
将构成逻辑单元的不同数据项组合成复合体。
struct <tag> { type member; };
typedef struct [tag] { ... } name_t;
标签总是和 struct 关键字一起使用,表达完整类型。区分同名标识符。用 typedef 定义类型别名,可与 tag 同名,或省略 tag。
如果类型相同,可以直接
type member1, member2;
,空结构struct{}
长度(sizeof)为 0。使用.
访问成员,指针使用->
访问。
int main (void) { typedef struct user { unsigned int id; char name[30]; unsigned char age; } user_t; struct user u = { .id = 1, .name = "Tom", .age = 10 }; user_t *p = &u; assert(p->age == u.age); return 0; }
在结构里存储字符串,注意 char[x]
和 char*
的差别。
struct d1 { char *s; // 指针 }; struct d2 { char s[30]; // 嵌入数组。 }; assert(sizeof(struct d1) == 8); assert(sizeof(struct d2) == 30);
赋值,或作为参数时,完整复制。
初始化
顺序初始化,或指定成员。
顺序初始化不要求全部成员赋值,未初始化值为零。如在 .member 后使用顺序初始化,则指向该 .member 后字段。(慎用!)强烈建议全部使用 .member 初始化方式。
struct user { int id; char name[30]; enum { WOMEN, MAN } sex; }; struct user u1 = { 100, "user1" }; assert(u1.sex == WOMEN); struct user u2 = { .name = "user1", MAN }; assert(u2.sex == MAN);
直接以复合字面量初始化指针。
struct user *p = &(struct user){ .id = 100 };
不完整类型
可在结构中定义指向自己的指针类型字段,甚至是尚未定义的其他类型指针字段。即便它们尚未完成,也无需前置声明。
不能把自己作为成员,因为类型不完整。但指针可以,这分属两种类型,且指针长度固定可知。同样因为不完整,所以必须用 struct tag,而不能是 typedef 别名。
int main (void) { struct A { int id; struct A *a; // 指向自己类型的指针成员(只能是 struct tag)。 struct B *b; // 指向尚未定义,也无前置声明的其他类型。 }; struct B { int id; }; struct A a = { .a = &(struct A){ .id = 100 }, .b = &(struct B){ .id = 200 }, }; assert(a.a->id == 100); assert(a.b->id == 200); return 0; }
匿名类型
可省略 tag 和 typedef 别名,定义匿名结构字段或变量。
注意:最后一个字段的分号结尾不能省略,否则编译器警告。
struct data { int id; struct { char *s; int x; } value; // 匿名结构字段。 }; struct data d = { .id = 1, .value = { "abc", 100 } }; assert(strcmp(d.value.s, "abc") == 0); assert(d.value.x == 100);
// 匿名结构变量 struct user { char *name; int age; } u = { .name = "tom", .age = 23 }; assert(strcmp(u.name, "tom") == 0);
偏移和对齐
首成员地址和结构相同,按各字段最长基础类型长度对齐。
对齐目的是提高 CPU 和内存访问效率。
member_address = struct_address + offset(member);
#include <stdio.h> #include <stddef.h> #include <stdalign.h> #include <assert.h> int main(void) { struct user { int id; char name[10]; struct { int a; long long b; } ext; }; assert(_Alignof(struct user) == 8); // long long assert(sizeof(struct user) == 32); assert(offsetof(struct user, id) == 0); assert(offsetof(struct user, name) == 4); assert(offsetof(struct user, ext) == 16); assert(offsetof(struct user, ext.a) == 16); assert(offsetof(struct user, ext.b) == 24); return 0; }
所有层次基本类型最长的是 longlong = 8,故按 alignof 8 对齐。 0 4 14 16 20 24 31 ==> offsetof +====+=========+========+========+========+=========+ | id | name | pad(2) | ext.a | pad(4) | ext.b | +====+=========+========+========+========+=========+ | | | | |--- 4 + 10 + pad(2) ---|--- 4 + pad(4) --|--- 8 ---| 2 * 8 8 8 ==> sizeof 32
因对齐缘故,结构长度不一定等于字段之和。且字段排列顺序不同,结构长度也会不同。
struct A { // alignof(int) = 4 char a; // 1 + pad(3) int b; // 4 char c; // 1 + pad(3) }; // sizeof = 12 assert(_Alignof(struct A) == sizeof(int)); assert(sizeof(struct A) == 12);
struct B { char a; // 1 char c; // 1 + pad(2), 和 a 合并后填充 2 个字节。 int b; // 4 }; assert(sizeof(struct B) == 8);
除此之外,还可人工干预。
#pragma pack(8) // 指示编译器按 8 字节对齐。 struct data { // 本应按 long double = 16 对齐。 char a; long double b; }; #pragma pack() // 取消自定义对齐。 assert(sizeof(long double) == 16); assert(_Alignof(struct data) == 8); assert(sizeof(struct data) == 24);
甚至取消对齐,以实际长度为准。( 不建议使用 )
struct data { char a; long double b; } __attribute__ ((packed)); assert(_Alignof(struct data) == 1); assert(sizeof(struct data) == 17);
弹性成员
结构最后一个成员,可以是未指定长度的数组类型(flexible array, T[]
)。但要注意,该弹性字段默认不分配内存。计算结构长度(sizeof)时,也不包括该字段。
header content +=======+=======//========+ | len | str .... | string = calloc(sizeof(header) + ?) +=======+=======//========+ 4 + ?
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> int main (void) { struct string { int len; char str[]; // [0] 相同;[1] 对齐后 8 字节,就不是弹性成员了。 }; // 总长度 = header + content + NULL; // 有了 header.len,确实不需要 NULL 结尾,但要考虑到 C 标准库函数。 size_t count = sizeof(struct string) + sizeof(char) * 10 + 1; assert(count == 15); // 分配内存,初始化头信息。 struct string *s = calloc(1, count); s->len = 10; // 填充内容。 memcpy(s->str, "hello", 5); assert(s->str[0] == 'h'); assert(s->str[4] == 'o'); // 显示字符串,如果没有 NULL 就麻烦了。 puts(s->str); // 释放内存。 free(s); return 0; }
赋值和传参时,弹性成员不会被复制。且不能直接对该成员赋值,因为没有分配内存。
struct string s2 = *s; // 只复制 sizeof 范围内数据,也就是 len。 memcpy(s2.str, s->str, 5); // *** stack smashing detected ***: terminated
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论