百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

C语言精华:结构体与联合体的灵活应用深度解析

yund56 2025-06-02 22:04 20 浏览



结构体(struct)和联合体(union)是C语言中两种重要的数据聚合类型,它们允许程序员将不同类型的数据项组合成一个逻辑单元。结构体用于将一组相关但可能不同类型的数据打包在一起,每个成员都有自己独立的内存空间。联合体则允许多个成员共享同一块内存空间,在任何时候只有一个成员是有效的。

熟练掌握结构体和联合体的灵活应用,包括嵌套、匿名成员、内存对齐与填充等概念,对于编写高效、清晰且能与底层硬件或数据格式紧密交互的C代码至关重要。

本文将深入探讨结构体和联合体的各种应用技巧,重点关注它们的嵌套使用、匿名特性,并详细解析内存对齐和填充的规则及其影响。

1. 结构体 (struct)

结构体用于将逻辑上相关的不同类型数据项捆绑在一起,形成一个新的复合数据类型。

1.1 基本定义与使用

 #include <stdio.h>
 #include <string.h>
 
 // 定义一个表示学生的结构体
 struct Student {
     char name[50];
     int id;
     float gpa;
 };
 
 int main() {
     // 声明结构体变量
     struct Student s1;
 
     // 访问和修改成员 (使用点运算符 .)
     strcpy(s1.name, "Alice");
     s1.id = 101;
     s1.gpa = 3.8f;
 
     printf("Student Name: %s\n", s1.name);
     printf("Student ID: %d\n", s1.id);
     printf("Student GPA: %.2f\n", s1.gpa);
 
     // 结构体初始化
     struct Student s2 = {"Bob", 102, 3.5f};
     printf("\nStudent Name: %s, ID: %d, GPA: %.2f\n", s2.name, s2.id, s2.gpa);
 
     // 指定成员初始化 (C99及以后)
     struct Student s3 = {
         .name = "Charlie",
         .id = 103,
         .gpa = 3.9f
     };
     printf("\nStudent Name: %s, ID: %d, GPA: %.2f\n", s3.name, s3.id, s3.gpa);
 
     // 结构体指针
     struct Student *ptr_s = &s1;
     // 访问成员 (使用箭头运算符 ->)
     printf("\nAccess via pointer: Name=%s\n", ptr_s->name);
     ptr_s->gpa = 3.85f; // 修改成员
     printf("Updated GPA: %.2f\n", s1.gpa);
 
     return 0;
 }

1.2 结构体嵌套

结构体的成员本身也可以是另一个结构体类型,这允许创建更复杂的数据结构。

 #include <stdio.h>
 #include <string.h>
 
 struct Date {
     int day;
     int month;
     int year;
 };
 
 struct Employee {
     char name[50];
     int employee_id;
     struct Date hire_date; // 嵌套 Date 结构体
     float salary;
 };
 
 int main() {
     struct Employee emp1;
 
     strcpy(emp1.name, "David");
     emp1.employee_id = 201;
     emp1.salary = 60000.0f;
 
     // 访问嵌套结构体的成员
     emp1.hire_date.day = 15;
     emp1.hire_date.month = 6;
     emp1.hire_date.year = 2022;
 
     printf("Employee: %s (ID: %d)\n", emp1.name, emp1.employee_id);
     printf("Hired on: %d/%d/%d\n", emp1.hire_date.month, emp1.hire_date.day, emp1.hire_date.year);
     printf("Salary: %.2f\n", emp1.salary);
 
     // 嵌套初始化
     struct Employee emp2 = {
         "Eve",
         202,
         {20, 8, 2023}, // 初始化嵌套结构体
         65000.0f
     };
 
     printf("\nEmployee: %s, Hired: %d/%d/%d\n",
            emp2.name, emp2.hire_date.month, emp2.hire_date.day, emp2.hire_date.year);
 
     return 0;
 }

1.3 匿名结构体 (Anonymous Structs)

匿名结构体是指在定义时没有给出结构体标签(名称)的结构体。它们通常作为另一个结构体或联合体的成员使用。

注意: C11 标准正式支持匿名结构体和联合体。在 C11 之前的编译器(如 GCC、Clang)可能通过扩展支持它们。

 #include <stdio.h>
 
 struct Point {
     int x;
     int y;
 };
 
 struct Rectangle {
     // 匿名结构体作为成员
     struct {
         int x;
         int y;
     } top_left; // 这个匿名结构体本身是有名字的 (top_left)
 
     // C11 匿名结构体 (无成员名)
     struct {
         int width;
         int height;
     }; // 这个匿名结构体的成员可以直接访问
 
     // 也可以嵌套已命名的结构体
     struct Point bottom_right;
 };
 
 int main() {
     struct Rectangle rect;
 
     // 访问第一个匿名结构体的成员 (通过其名称 top_left)
     rect.top_left.x = 10;
     rect.top_left.y = 20;
 
     // 访问 C11 匿名结构体的成员 (直接访问)
     rect.width = 100;
     rect.height = 50;
 
     // 访问嵌套的已命名结构体成员
     rect.bottom_right.x = rect.top_left.x + rect.width;
     rect.bottom_right.y = rect.top_left.y + rect.height;
 
     printf("Rectangle Top-Left: (%d, %d)\n", rect.top_left.x, rect.top_left.y);
     printf("Rectangle Dimensions: Width=%d, Height=%d\n", rect.width, rect.height);
     printf("Rectangle Bottom-Right: (%d, %d)\n", rect.bottom_right.x, rect.bottom_right.y);
 
     return 0;
 }

优点:

  • 减少层级:对于 C11 的无名匿名结构体,可以减少访问成员时的层级(如 rect.width 而不是 rect.dimensions.width)。
  • 组织相关数据:可以将一组密切相关的成员组织在一起,即使它们逻辑上属于外部结构。

2. 联合体 (union)

联合体允许其所有成员共享同一块内存区域。联合体的大小由其最大成员的大小决定(可能因对齐而更大)。在任何时刻,只有一个成员可以被有效地存储和访问。

2.1 基本定义与使用

 #include <stdio.h>
 
 // 定义一个可以存储整数、浮点数或字符的联合体
 union Data {
     int i;
     float f;
     char c;
 };
 
 int main() {
     union Data data;
 
     printf("Size of union Data: %zu bytes\n", sizeof(union Data));
     // 大小通常是 sizeof(int) 或 sizeof(float) 中较大的那个 (通常是 4 或 8)
 
     data.i = 10;
     printf("Data as int: %d\n", data.i);
 
     // 此时访问 f 或 c 是未定义行为 (UB),但常用于类型转换 (Type Punning)
     // printf("Data as float (UB): %f\n", data.f);
 
     data.f = 3.14f;
     printf("Data as float: %f\n", data.f);
     // 此时访问 i 或 c 是 UB
     // printf("Data as int (UB): %d\n", data.i);
 
     data.c = 'A';
     printf("Data as char: %c\n", data.c);
 
     // 联合体初始化 (只能初始化第一个成员)
     union Data data2 = { 20 }; // 初始化 data2.i 为 20
     printf("\nInitialized data2.i: %d\n", data2.i);
 
     // 指定成员初始化 (C99及以后)
     union Data data3 = { .f = 2.71f }; // 初始化 data3.f
     printf("Initialized data3.f: %f\n", data3.f);
 
     return 0;
 }

2.2 联合体的应用场景

  1. 节省内存:当一组数据项互斥,即任何时候只需要使用其中一个时,联合体可以节省大量内存。
  2. struct Packet {
    int type; // 标识 data 中存储的是哪种类型
    union {
    int command_id;
    float sensor_value;
    char message[64];
    }
    data; // 匿名联合体 (C11)
    };

    // 如果不用联合体,需要为 command_id, sensor_value, message[64] 都分配空间
    // 使用联合体,只需要分配 max(sizeof(int), sizeof(float), sizeof(char[64])) 的空间
  3. 类型转换/重新解释位模式 (Type Punning):通过联合体,可以用一种类型的成员写入数据,然后用另一种类型的成员读出数据,从而重新解释底层的二进制位模式。这是未定义行为 (Undefined Behavior) 在 C 标准中,但在实践中被广泛用于某些底层编程技巧,尤其是在与硬件交互或需要特定位操作时。 编译器可能会进行优化,导致结果不符合预期。更安全、标准的类型转换方式是使用 memcpy
     #include <stdio.h>
     #include <string.h>
 
     union FloatInt {
         float f;
         unsigned int i;
     };
 
     int main() {
         union FloatInt fi;
         fi.f = -1.0f;
 
         // 通过联合体查看 float 的二进制表示 (依赖于实现)
         printf("Float: %f, Int representation: 0x%X\n", fi.f, fi.i);
 
         // 更安全的方式:使用 memcpy
         float f_val = -1.0f;
         unsigned int i_val;
         // 确保 sizeof(float) == sizeof(unsigned int)
         memcpy(&i_val, &f_val, sizeof(float));
         printf("Float: %f, Int representation (memcpy): 0x%X\n", f_val, i_val);
 
         return 0;
     }
  1. 实现变体类型 (Variant Types):结合结构体和枚举,可以创建能存储多种不同类型值的变体类型。
     #include <stdio.h>
     #include <stdlib.h>
     #include <string.h>
     
     typedef enum { TYPE_INT, TYPE_FLOAT, TYPE_STRING } VariantType;
     
     typedef struct {
         VariantType type;
         union {
             int i;
             float f;
             char *s; // 注意:字符串需要动态分配和释放
         } value;
     } Variant;
     
     void print_variant(const Variant *v) {
         switch (v->type) {
             case TYPE_INT:
                 printf("Int: %d\n", v->value.i);
                 break;
             case TYPE_FLOAT:
                 printf("Float: %f\n", v->value.f);
                 break;
             case TYPE_STRING:
                 printf("String: \"%s\"\n", v->value.s ? v->value.s : "(null)");
                 break;
             default:
                 printf("Unknown type\n");
         }
     }
     
     void free_variant_string(Variant *v) {
         if (v->type == TYPE_STRING && v->value.s) {
             free(v->value.s);
             v->value.s = NULL;
         }
     }
     
     int main() {
         Variant v1, v2, v3;
     
         v1.type = TYPE_INT;
         v1.value.i = 123;
     
         v2.type = TYPE_FLOAT;
         v2.value.f = 45.6f;
     
         v3.type = TYPE_STRING;
         v3.value.s = strdup("Hello Variant"); // 需要释放
     
         print_variant(&v1);
         print_variant(&v2);
         print_variant(&v3);
     
         free_variant_string(&v3);
     
         return 0;
     }

2.3 匿名联合体 (Anonymous Unions)

与匿名结构体类似,匿名联合体通常作为结构体或联合体的成员使用,允许直接访问其成员,而无需通过联合体名称。

 #include <stdio.h>
 
 struct Configuration {
     int config_type; // 0: Network, 1: Serial
     union { // C11 匿名联合体
         struct { // 匿名结构体
             char ip_address[16];
             int port;
         } network_config;
         struct { // 匿名结构体
             char device_name[32];
             int baud_rate;
         } serial_config;
     }; // 匿名联合体的成员可以直接访问
 };
 
 int main() {
     struct Configuration cfg;
 
     cfg.config_type = 0; // Network config
     // 直接访问匿名联合体内的匿名结构体成员
     strcpy(cfg.network_config.ip_address, "192.168.1.100");
     cfg.network_config.port = 8080;
 
     printf("Config Type: Network\n");
     printf("IP: %s, Port: %d\n", cfg.network_config.ip_address, cfg.network_config.port);
 
     cfg.config_type = 1; // Serial config
     strcpy(cfg.serial_config.device_name, "/dev/ttyS0");
     cfg.serial_config.baud_rate = 9600;
 
     printf("\nConfig Type: Serial\n");
     printf("Device: %s, Baud Rate: %d\n", cfg.serial_config.device_name, cfg.serial_config.baud_rate);
 
     return 0;
 }

3. 内存对齐 (Memory Alignment)

内存对齐是指数据在内存中存储的起始地址必须是某个值(通常是数据类型大小或CPU架构要求的特定值)的倍数。CPU访问对齐的数据通常比访问未对齐的数据更快,甚至某些架构(如ARM的某些模式)根本不允许访问未对齐的数据,会触发硬件异常。

编译器为了满足CPU的对齐要求,并优化访问速度,会自动在结构体成员之间或结构体末尾插入填充字节 (Padding)

3.1 对齐规则 (常见规则,具体依赖编译器和架构)

  1. 结构体成员的对齐:每个成员变量的存储起始地址必须是其自身类型大小默认对齐值 (通常由架构决定,如4或8字节)较小者的整数倍。
  2. 例如,char 通常按 1 字节对齐,short 按 2 字节,int 按 4 字节,double 按 8 字节。
  3. 结构体的整体对齐:整个结构体的总大小必须是其所有成员中最大对齐要求的整数倍。这个最大对齐要求称为结构体的对齐模数 (Alignment Modulus)

3.2 填充示例

 #include <stdio.h>
 #include <stddef.h> // For offsetof
 
 struct Example1 {
     char c1;   // 大小 1, 对齐 1
     int i;     // 大小 4, 对齐 4
     char c2;   // 大小 1, 对齐 1
 };
 // 布局分析 (假设 int 对齐为 4):
 // c1: offset 0, size 1
 // padding: offset 1, size 3 (为了让 i 在 4 的倍数地址开始)
 // i:  offset 4, size 4
 // c2: offset 8, size 1
 // padding: offset 9, size 3 (为了让整个结构体大小是最大对齐要求 4 的倍数)
 // Total size = 12
 
 struct Example2 {
     char c1;   // 大小 1, 对齐 1
     char c2;   // 大小 1, 对齐 1
     int i;     // 大小 4, 对齐 4
 };
 // 布局分析:
 // c1: offset 0, size 1
 // c2: offset 1, size 1
 // padding: offset 2, size 2 (为了让 i 在 4 的倍数地址开始)
 // i:  offset 4, size 4
 // Total size = 8 (已经是最大对齐要求 4 的倍数,无需末尾填充)
 
 int main() {
     printf("Size of Example1: %zu bytes\n", sizeof(struct Example1)); // 通常输出 12
     printf("Offsets in Example1:\n");
     printf("  c1: %zu\n", offsetof(struct Example1, c1)); // 0
     printf("  i:  %zu\n", offsetof(struct Example1, i));  // 4
     printf("  c2: %zu\n", offsetof(struct Example1, c2)); // 8
 
     printf("\nSize of Example2: %zu bytes\n", sizeof(struct Example2)); // 通常输出 8
     printf("Offsets in Example2:\n");
     printf("  c1: %zu\n", offsetof(struct Example2, c1)); // 0
     printf("  c2: %zu\n", offsetof(struct Example2, c2)); // 1
     printf("  i:  %zu\n", offsetof(struct Example2, i));  // 4
 
     return 0;
 }

offsetof:定义在 <stddef.h> 中,用于获取结构体成员相对于结构体起始地址的字节偏移量。

3.3 影响与优化

  • 内存占用增加:填充字节会增加结构体的实际内存占用。
  • 性能提升:保证对齐可以提高CPU访问效率。
  • 结构体成员顺序:调整结构体成员的声明顺序可以影响填充字节的数量,从而可能减小结构体总大小。通常将对齐要求较高的成员放在前面,或者将对齐要求相同的成员放在一起,有助于减少填充。 (如 Example2Example1 更紧凑)。

3.4 控制对齐

虽然默认对齐通常是最佳选择,但有时需要显式控制对齐:

  • #pragma pack(n) (编译器特定):指示编译器以 n 字节对齐。#pragma pack(1) 常用于创建紧凑的、无填充的结构体,例如用于文件格式或网络协议,但这可能牺牲性能并导致未对齐访问。
     #pragma pack(push, 1) // 保存当前对齐设置,并设置为 1 字节对齐
     struct PackedStruct {
         char c1;
         int i;
         char c2;
     };
     #pragma pack(pop) // 恢复之前的对齐设置
     
     // sizeof(PackedStruct) 通常为 1 + 4 + 1 = 6
  • _Alignas / alignas (C11及以后):用于指定变量或类型的最小对齐要求。
     #include <stdalign.h> // For alignas
     
     struct AlignedStruct {
         alignas(16) int i; // 要求 i 至少按 16 字节对齐
         char c;
     };
     // 结构体的整体对齐要求会提升到 16
  • aligned_alloc (C11及以后):分配具有指定对齐方式的内存。

注意: 修改默认对齐方式可能导致性能下降或未对齐访问问题,应谨慎使用,并充分了解目标平台的特性。

4. 总结

  • 结构体 (struct) 将不同类型数据组合,成员各自独立存储,支持嵌套和匿名成员(C11)。
  • 联合体 (union) 将不同类型数据组合,成员共享同一内存,大小由最大成员决定,常用于节省内存、类型转换(需谨慎)和变体类型。
  • 内存对齐 是编译器为了优化性能和满足硬件要求而采取的策略,会在结构体中插入填充字节
  • 结构体成员的声明顺序会影响填充和总大小,合理安排顺序可以优化内存占用。
  • 可以通过 #pragma packalignas (C11) 等方式控制对齐,但这可能影响性能和可移植性。

理解结构体、联合体以及内存对齐的原理和应用,是编写高效、健壮且能与底层细节交互的C代码的关键。在追求内存紧凑性和性能时,需要权衡对齐带来的影响,并根据具体场景选择最合适的实现方式。

相关推荐

SM小分队Girls on Top,女神战队少了f(x)?

这次由SM娱乐公司在冬季即将开演的smtown里,将公司的所有女团成员集结成了一个小分队project。第一位这是全面ACE的大姐成员权宝儿(BoA),出道二十年,在日本单人销量过千万,韩国国内200...

韩国女团 aespa 首场 VR 演唱会或暗示 Quest 3 将于 10 月推出

AmazeVR宣布将在十月份举办一场现场VR音乐会,观众将佩戴MetaQuest3进行体验。韩国女团aespa于2020年11月出道,此后在日本推出了三张金唱片,在韩国推出了...

韩网热议!女团aespa成员Giselle在长腿爱豆中真的是legend

身高163的Giselle,长腿傲人,身材比例绝了...

假唱而被骂爆的女团:IVE、NewJeans、aespa上榜

在韩国,其实K-pop偶像并不被认为是真正的歌手,因为偶像们必须兼备舞蹈能力、也经常透过对嘴来完成舞台。由于科技的日渐发达,也有许多网友会利用消音软体来验证K-pop偶像到底有没有开麦唱歌,导致假唱这...

新女团Aespa登时尚大片 四个少女四种style

来源:环球网

韩国女团aespa新歌MV曝光 画面梦幻造型超美

12月20日,韩国女团aespa翻唱曲《DreamsComeTrue》MV公开,视频中,她们的造型超美!WINTER背后长出一双梦幻般的翅膀。柳智敏笑容甜美。宁艺卓皮肤白皙。GISELLE五官精致...

女网友向拳头维权,自称是萨勒芬妮的原型?某韩国女团抄袭KDA

女英雄萨勒芬妮(Seraphine)是拳头在2020年推出的第五位新英雄,在还没有正式上线时就备受lsp玩家的关注,因为她实在是太可爱了。和其他新英雄不同的是,萨勒芬妮在没上线时就被拳头当成虚拟偶像来...

人气TOP女团是?INS粉丝数见分晓;TWICE成员为何在演唱会落泪?

现在的人气TOP女团是?INS粉丝数见分晓!现在爱豆和粉丝之间的交流方法变得多种多样,但是Instagram依然是主要的交流手段。很多粉丝根据粉丝数评价偶像的人气,拥有数百、数千万粉丝的组合作为全球偶...

韩国女团MVaespa Drama MV_韩国女团穿超短裙子跳舞

WelcometoDrama.Pleasefollow4ruleswhilewatchingtheDrama.·1)Lookbackimmediatelywhenyoufe...

aespa师妹团今年将出道! SM职员亲口曝「新女团风格、人数」

记者刘宛欣/综合报导南韩造星工厂SM娱乐曾打造出东方神起、SUPERJUNIOR、少女时代、SHINee、EXO等传奇团体,近年推出的aespa、RIIZE更是双双成为新生代一线团体,深受大众与粉丝...

南韩最活跃的女团aespa,新专辑《Girls》即将发布,盘点昔日经典

女团aespa歌曲盘点,新专辑《Girls》即将发布,期待大火。明天也就是2022年的7月8号,aespa新专辑《Girls》即将发行。这是继首张专辑《Savage》之后,时隔19个月的第二张专辑,这...

章泽天女团aespa出席戛纳晚宴 宋康昊携新片亮相

搜狐娱乐讯(山今/文玄反影/图科明/视频)法国时间5月23日晚,女团aespa、宋康昊、章泽天等明星亮相戛纳晚宴。章泽天身姿优越。章泽天肩颈线优越。章泽天双臂纤细。章泽天仪态端正。女团aespa亮...

Aespa舞台暴露身高比例,宁艺卓脸大,柳智敏有“TOP”相

作为SM公司最新女团aespa,初舞台《BlackMamba》公开,在初舞台里,看得出来SM公司是下了大功夫的,虽然之前SM公司新出的女团都有很长的先导片,但是aespa显然是有“特殊待遇”。运用了...

AESPA女团成员柳智敏karina大美女

真队内速度最快最火达成队内首个且唯一两百万点赞五代男女团中输断层第一(图转自微博)...

对来学校演出的女团成员语言性骚扰?韩国这所男高的学生恶心透了

哕了……本月4日,景福男子高中相关人士称已经找到了在SNS中上传对aespa成员进行性骚扰文章的学生,并开始着手调查。2日,SM娱乐创始人李秀满的母校——景福高中迎来了建校101周年庆典活动。当天,S...