计算机二级(C语言)考试高频考点总汇(三)—— 结构体和共用体、结构体对齐规则、联合体大小计算
目录
九、结构体和共用体
十、结构体对齐规则
十一、联合体大小计算
九、结构体和共用体
141. 结构体是(不同类型成员的集合),是⼀种用户自定义的数据类型,可以将不同类型的成员组合在⼀起,用于表示(复杂的数据结构)。
142. 结构体成员在内存中是(连续存储的),这意味着可以使用(指针运算)来访问结构体成员。
143. 结构体成员可以通过(.运算符)访问, . 运算符用于访问结构体变量的成员,例如,
student.name 表示访问 student 结构体变量的 name 成员。
144. 结构体指针可以通过(->运算符)访问成员, -> 运算符用于访问结构体指针指向的结构体变量的成员,例如, studentPtr->name 表示访问 studentPtr 指针指向的结构体变量的name 成员。
145. 结构体可以(嵌套),结构体可以嵌套在另一个结构体内部,形成嵌套的结构体,用于表示(更复杂的数据结构)。
146. 可以使用(typedef关键字)为结构体定义别名,这可以简化结构体类型的使用,例如,
typedef struct { int x, y; } Point; 。
147. 可以使用(位域)在结构体中定义占用(特定位数的成员),位域可以(节省内存空间),但使用时需要注意(可移植性)。
148. 共用体是(不同类型成员共享同一内存位置)的数据结构,这意味着在任何给定时间,共用体只能存储(一个成员的值)。
149. 枚举类型使用(enum关键字)定义,用于定义一组(命名的整数常量),可以提高代码的(可读性)。
150. 可以使用(sizeof运算符)获取结构体或共用体的大小,结构体的大小可能受到(内存对齐)的影响。
151. 内存对齐是指将数据存储在内存地址是其大小的倍数的位置,这可以提高程序的(性能)。
152. 结构体可以包含(指向自身的指针),这允许创建(链表)、(树)等(递归数据结构)。
153. 结构体可以包含(函数指针),这允许在结构体中存储(函数),实现(多态性)。
154. 可以使用(柔性数组)作为结构体的最后⼀个成员,柔性数组的大小可以在(运行时动态确
定)。
155. 结构体和联合体的大小受到(内存对齐)的影响,可以使用 #pragma pack 控制对齐方式。
156. (结构体是用户自定义的复合数据类型):结构体可以将多个不同类型的数据组合成⼀个整体, 方便管理和操作。(例如,可以定义⼀个 struct Student 结构体,包含学生的姓名、年龄、学号等信息。)
• 示例:
#include <stdio.h>
#include <string.h>
struct Student
{
char name[50];
int age;
float gpa;
};
int main()
{
struct Student s1;
strcpy(s1.name, "Alice");
s1.age = 20;
s1.gpa = 3.8;
printf("Name: %s, Age: %d, GPA: %.2f\n", s1.name, s1.age, s1.gpa);
return 0;
}
157. (结构体指针用于间接访问结构体成员):通过结构体指针可以方便地访问和修改结构体成员, 尤其是在函数参数传递时,可以避免结构体拷贝的开销。(例如, struct Student *p = &s1; 定义了一个指向 Student 结构体的指针,可以通过 p->name 访问 s1.name 。)
• 示例:
#include <stdio.h>
#include <string.h>
struct Student
{
char name[50];
int age;
float gpa;
};
void printStudent(struct Student *s)
{
printf("Name: %s, Age: %d, GPA: %.2f\n", s->name, s->age, s->gpa);
}
int main()
{
struct Student s1;
strcpy(s1.name, "Alice");
s1.age = 20;
s1.gpa = 3.8;
printStudent(&s1);
return 0;
}
158. (结构体数组用于存储多个结构体变量):结构体数组可以方便地存储和管理多个结构体变量, 例如存储学生信息、商品信息等。(例如, struct Student students[100]; 定义了一个包含 100 个 Student 结构体的数组。)
• 示例:
#include <stdio.h>
#include <string.h>
struct Student
{
char name[50];
int age;
float gpa;
};
int main()
{
struct Student students[3];
strcpy(students[0].name, "Alice");
students[0].age = 20;
students[0].gpa = 3.8;
strcpy(students[1].name, "Bob");
students[1].age = 21;
students[1].gpa = 3.5;
strcpy(students[2].name, "Charlie");
students[2].age = 19;
students[2].gpa = 3.9;
for (int i = 0; i < 3; i++)
{
printf("Name: %s, Age: %d, GPA: %.2f\n", students[i].name,
students[i].age, students[i].gpa);
}
return 0;
}
159. (结构体嵌套允许结构体包含其他结构体作为成员):结构体嵌套可以构建更复杂的数据结构, 例如树、链表等。(例如,可以定义⼀个 struct Address 结构体,包含街道、城市、邮编等信息,然后在 struct Student 结构体中包含⼀个 struct Address 类型的成员。)
• 示例:
#include <stdio.h>
#include <string.h>
struct Address
{
char street[100];
char city[50];
char zip[10];
};
struct Student
{
char name[50];
int age;
float gpa;
struct Address address;
};
int main()
{
struct Student s1;
strcpy(s1.name, "Alice");
s1.age = 20;
s1.gpa = 3.8;
strcpy(s1.address.street, "123 Main St");
strcpy(s1.address.city, "Anytown");
strcpy(s1.address.zip, "12345");
printf("Name: %s, Age: %d, GPA: %.2f, Address: %s, %s, %s\n",
s1.name, s1.age, s1.gpa, s1.address.street, s1.address.city,
s1.address.zip);
return 0;
}
160. (结构体对齐影响结构体的大小):结构体对齐是为了提高CPU访问数据的效率,编译器会在结构体成员之间插入填充字节,导致结构体的大小可能大于成员大小之和。(例如,如果 int 类
型占用 4 个字节, char 类型占用 1 个字节,则包含一个 int 和一个 char 成员的结构体的大小可能不是 5 个字节,而是 8 个字节。)
• 示例:
#include <stdio.h>
struct Test
{
char a;
int b;
char c;
};
int main()
{
printf("Size of struct Test: %lu\n", sizeof(struct Test)); // 输出可能是 12
return 0;
}
161. (可以使用 #pragma pack 指令控制结构体对齐):通过 #pragma pack 指令可以指定
结构体的对齐方式,可以用于减小结构体的大小,但可能会降低CPU访问数据的效率。(例
如, #pragma pack(1) 表示结构体按照 1 字节对齐。)
• 示例:
#include <stdio.h>
#pragma pack(1) // 按照 1 字节对⻬
struct Test
{
char a;
int b;
char c;
};
#pragma pack() // 恢复默认对⻬⽅式
int main()
{
printf("Size of struct Test: %lu\n", sizeof(struct Test)); // 输出可能是 6
return 0;
}
162. (联合体是⼀种特殊的数据类型,多个成员共享同⼀块内存空间):联合体可以用于节省内存空间,但在同⼀时刻只能存储⼀个成员的值。(例如,可以定义⼀个 union Data 联合体,包含 int 、 float 和 char 三个成员,它们共享同⼀块内存空间。)
• 示例:
#include <stdio.h>
union Data
{
int i;
float f;
char str[20];
};
int main()
{
union Data data;
data.i = 10;
printf("data.i: %d\n", data.i);
data.f = 220.5;
printf("data.f: %.1f\n", data.f);
printf("data.i: %d\n", data.i); // data.i 的值被覆盖了
return 0;
}
163. (联合体的大小取决于最大成员的大小):联合体的大小等于其最大成员的大小,以便能够存储任何⼀个成员的值。(例如,如果 int 类型占用 4 个字节, float 类型占用 4 个字节,char[20] 类型占用 20 个字节,则包含这三个成员的联合体的大小为 20 个字节。)
• 示例:
#include <stdio.h>
union Data
{
int i;
float f;
char str[20];
};
int main()
{
printf("Size of union Data: %lu\n", sizeof(union Data)); // 输出 20
return 0;
}
164. (可以使用结构体和联合体构建复杂的数据结构):结构体和联合体可以组合使用,构建更复杂的数据结构,例如链表、树、图等。(例如,可以使用结构体表示链表节点,其中包含数据和指向下⼀个节点的指针;可以使用联合体表示节点的数据,可以是整数、浮点数或字符串。)
• 示例: 链表节点
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node *next;
};
int main()
{
struct Node *head = (struct Node *)malloc(sizeof(struct Node));
head->data = 10;
head->next = NULL;
printf("Data: %d\n", head->data);
free(head);
return 0;
}
165. (位域允许结构体成员占用指定的位数):位域可以用于节省内存空间,尤其是在嵌入式系统编程中,可以精确控制数据的存储方式。(例如,可以定义⼀个结构体,其中包含多个标志位,每个标志位只占用 1 位。)
• 示例:
#include <stdio.h>
struct Flags
{
unsigned int flag1 : 1; // 占⽤ 1 位
unsigned int flag2 : 1; // 占⽤ 1 位
unsigned int flag3 : 1; // 占⽤ 1 位
};
int main()
{
struct Flags flags;
flags.flag1 = 1;
flags.flag2 = 0;
flags.flag3 = 1;
printf("Flag1: %d, Flag2: %d, Flag3: %d\n", flags.flag1, flags.flag2,flags.flag3);
printf("Size of struct Flags: %lu\n", sizeof(struct Flags)); // ⼤⼩通常是4 字节
return 0;
}
166. (结构体内存对齐的极致细节与 #pragma pack 的高级用法):
💡• (默认对齐值)不同的编译器和平台可能有不同的默认对齐值(通常是 4 或 8 字节)。
• (成员顺序的影响)合理安排结构体成员的顺序可以减少填充字节,从而减小结构体的大小。
• (嵌套结构体的对齐) 嵌套结构体的对齐会受到外层结构体和内层结构体对齐规则的影 响。
• #pragma pack 的高级用法:
💡 • 条件编译: 可以使用条件编译指令 #ifdef 、 #ifndef 等,在不同的平台或编译器上使用不同的 #pragma pack 值。
• 作用域控制: #pragma pack 的作用域从指令开始到 #pragma pack() 结束, 或者到文件末尾。可以使用命名空间或头文件来控制 #pragma pack 的作用域。
• 示例:
#include <stdio.h>
#ifdef _WIN32 // Windows 平台
#pragma pack(push, 8) // 保存当前对⻬值,并设置为 8 字节对⻬
#else // 其他平台
#pragma pack(push, 4) // 保存当前对⻬值,并设置为 4 字节对⻬
#endif
struct Nested
{
char a; // 1 byte
int b; // 4 bytes
};
struct Outer
{
char x; // 1 byte
struct Nested n; //8 bytes 对⻬到 Nested 的最⼤成员⼤⼩ (4)
double y; // 8 bytes
};
#pragma pack(pop) // 恢复之前的对⻬
int main()
{
printf("Size of Nested: %zu\n", sizeof(struct Nested)); // Windows: 8, Others: 8
printf("Size of Outer: %zu\n", sizeof(struct Outer)); // Windows: 24, Others: 20
return 0;
}
此示例展示了如何使用条件编译和 #pragma pack 指令,在不同的平台上设置不同的对齐值。
#pragma pack(push, n) 用于保存当前的对齐值,并将其设置为 n 字节对齐。
#pragma pack(pop) 用于恢复之前保存的对齐值。
167. (结构体位域的跨平台兼容性与手动位操作):
• 跨平台兼容性:
💡 • 位域的存储顺序: 不同的编译器可能使用不同的位域存储顺序(从高位到低位,或者从低位到高位),这会导致跨平台兼容性问题。
• 位域的对齐: 不同的编译器可能对位域进行不同的对齐,这也会导致跨平台兼容性问题。
• 手动位操作:
💡 • 为了保证跨平台兼容性,可以使用手动位操作来代替位域。手动位操作使用位运算符
( & 、 | 、 ^ 、 ~ 、 << 、 >> )来操作数据的位。
• 示例:
#include <stdio.h>
// 定义一个结构体 Flags,用于存储多个标志位
struct Flags
{
unsigned int data; // 使用一个无符号整型变量 data 来存储标志位
};
// 定义位掩码,每个掩码对应一个标志位
#define FLAG1_MASK 0x01 // 00000001,对应第 0 位
#define FLAG2_MASK 0x02 // 00000010,对应第 1 位
#define FLAG3_MASK 0x04 // 00000100,对应第 2 位
// 设置标志位的函数
void setFlag(struct Flags *flags, int flagMask)
{
flags->data |= flagMask; // 使用按位或操作将指定的标志位设置为 1
}
// 清除标志位的函数
void clearFlag(struct Flags *flags, int flagMask)
{
flags->data &= ~flagMask; // 使用按位与和按位取反操作将指定的标志位清除为 0
}
// 检查标志位的函数
int checkFlag(struct Flags *flags, int flagMask)
{
return (flags->data & flagMask) != 0; // 使用按位与操作检查指定的标志位是否为 1
}
int main()
{
struct Flags flags = {0}; // 初始化 Flags 结构体,data 被设置为 0,所有标志位为 0
setFlag(&flags, FLAG1_MASK); // 设置第 0 位(FLAG1)为 1
setFlag(&flags, FLAG3_MASK); // 设置第 2 位(FLAG3)为 1
// 打印当前所有标志位的状态
printf("Flag1: %d, Flag2: %d, Flag3: %d\n",
checkFlag(&flags, FLAG1_MASK), // 检查 FLAG1 是否为 1
checkFlag(&flags, FLAG2_MASK), // 检查 FLAG2 是否为 1
checkFlag(&flags, FLAG3_MASK)); // 检查 FLAG3 是否为 1
clearFlag(&flags, FLAG1_MASK); // 清除第 0 位(FLAG1)为 0
// 再次打印当前所有标志位的状态
printf("Flag1: %d, Flag2: %d, Flag3: %d\n",
checkFlag(&flags, FLAG1_MASK), // 检查 FLAG1 是否为 1
checkFlag(&flags, FLAG2_MASK), // 检查 FLAG2 是否为 1
checkFlag(&flags, FLAG3_MASK)); // 检查 FLAG3 是否为 1
return 0; // 程序正常结束
}
此示例展示了如何使用手动位操作来代替位域,从而保证跨平台兼容性。通过定义位掩码和使用位运算符,可以精确地控制数据的位,而无需依赖编译器的位域实现。
十、结构体对齐规则
168. (结构体对齐的目的是提高CPU访问效率):CPU在读取内存数据时,通常以字(word)为单位进行读取。为了提高读取效率,编译器会对结构体成员进行对齐,使得每个成员都位于合适的
地址上。(例如,如果CPU以4字节为单位读取数据,那么最好将 int 类型的成员放在地址是4的倍数的位置上。)
169. (结构体成员的对齐规则):
💡 • (每个成员的起始地址必须是其自身大小的整数倍):如果不是,编译器会在成员前面填充(padding)若干字节。
• (结构体的总大小必须是其最大成员大小的整数倍):如果不是,编译器会在结构体末尾
填充若干字节。
• (嵌套结构体的对齐):嵌套结构体的对齐规则与普通结构体相同,需要考虑嵌套结构体
本身的大小和对齐要求。
• ( #pragma pack(n) 可以指定结构体按照 n 字节对齐):其中 n 必须是 2 的幂次
方(1, 2, 4, 8, 16)。使用 #pragma pack() 可以恢复默认对齐方式。
• (对齐规则可能因编译器和平台而异):不同的编译器和平台可能有不同的默认对齐值和
对齐规则。
170. (基本数据类型的对齐值):
💡 • char : 1字节对齐
• short : 2字节对齐
• int : 4字节对齐
• long : 在32位系统中通常是4字节对齐,在64位系统中通常是8字节对齐
• float : 4字节对齐
• double : 8字节对齐
• 指针类型:4字节(32位系统)或8字节(64位系统)对齐
171. (计算结构体大小的步骤):
💡 1. (确定每个成员的对齐值):根据成员的类型和编译器的默认对齐值,确定每个成员的对齐值。
2. (计算每个成员的偏移量):从结构体的起始地址开始,依次计算每个成员的偏移量。偏
移量必须是该成员对齐值的整数倍,如果不是,则需要填充若干字节。
3. (计算结构体的总大小):结构体的总大小必须是其最大成员大小的整数倍,如果不是, 则需要在结构体末尾填充若干字节。
172. (结构体对齐的影响):
💡 • (内存占用):结构体对齐会影响结构体的大小,可能导致内存浪费。
• (程序性能):合理的结构体对齐可以提高CPU访问数据的效率,从而提高程序性能。
• (跨平台兼容性):不同的编译器和平台可能有不同的对齐规则,这会导致跨平台兼容性
问题。
173. (使用 #pragma pack 减少内存占用):
💡 • ( #pragma pack(1) 可以使结构体成员紧密排列):这可以减少内存占用,但可能会降低CPU访问数据的效率。
• (需要谨慎使用 #pragma pack ):过度使用 #pragma pack 可能会导致程序性能下降,甚至出现错误。
• (示例):
#include <stdio.h>
#pragma pack(1) // 按照 1 字节对⻬
struct Test
{
char a;
int b;
char c;
};
#pragma pack() // 恢复默认对⻬⽅式
int main()
{
printf("Size of struct Test: %lu\n", sizeof(struct Test)); // 输出可能是 6
return 0;
}
174. (结构体对齐的示例分析):
#include <stdio.h>
struct S1
{
char a; // 1 byte
int b; // 4 bytes
char c; // 1 byte
};
struct S2
{
char a; // 1 byte
char c; // 1 byte
int b; // 4 bytes
};
int main()
{
printf("Size of S1: %lu\n", sizeof(struct S1)); // 可能输出 12 (1+3padding + 4 + 1+3 padding)
printf("Size of S2: %lu\n", sizeof(struct S2)); // 可能输出 8 (1 + 1 + 2padding + 4)
return 0;
}
😀 • (分析过程):结构体 S1 中, char a 后面会填充 3 个字节, char c 后面也会填充 3 个字节,以保证 int b 的地址是 4 的倍数,结构体总大小是 4 的倍数。而结构体S2 通过调整成员顺序,减少了填充字节,从而减小了结构体的大小。
175. (嵌套结构体的对齐示例):
#include <stdio.h>
struct Inner
{
char a; // 1 byte
short b; // 2 bytes
}; // Size: 4 (1 + 1 padding + 2)
struct Outer
{
int x; // 4 bytes
struct Inner y; // 4 bytes
double z; // 8 bytes
}; // Size: 16 (4 + 4 + 8)
int main()
{
printf("Size of Inner: %lu\n", sizeof(struct Inner));
printf("Size of Outer: %lu\n", sizeof(struct Outer));
return 0;
}
😀 • (分析过程): Inner 结构体的大小为 4 字节(1 + 1 padding + 2), Outer 结构体
中 Inner y 成员的偏移量为 4,满足对齐要求。 Outer 结构体的大小为 16 字节(4 + 4 + 8),是其最大成员 double 的大小的整数倍。
176. (如何优化结构体成员顺序以减少内存占用):
😀 • (将相同类型的成员放在⼀起):这样可以减少填充字节。
• (将较小的成员放在前面):这样可以避免较大的成员对齐导致的内存浪费。
• (示例):
#include <stdio.h>
struct BadOrder
{
char a;
int b;
char c;
}; // Size: 12
struct GoodOrder
{
char a;
char c;
int b;
}; // Size: 8
int main()
{
printf("Size of BadOrder: %lu\n", sizeof(struct BadOrder));
printf("Size of GoodOrder: %lu\n", sizeof(struct GoodOrder));
return 0;
}
😀
• (分析过程):通过调整结构体成员的顺序,可以将结构体的大小从 12 字节减少到 8 字
节。
177. (结构体对齐与跨平台兼容性):
😀 • (不同的编译器和平台可能有不同的对齐规则):这会导致在不同的平台上编译的程序, 其结构体大小可能不同。
• (可以使用 #pragma pack 来强制指定对齐方式):但这可能会导致程序性能下降, 并且在某些平台上可能无效。
• (为了保证跨平台兼容性,最好避免依赖特定的对齐规则):可以使用手动位操作来代替
位域,或者使用标准库提供的数据类型。
十一、联合体大小计算
178. (联合体的本质:共享内存空间):联合体 (union) 是⼀种特殊的数据类型,它允许在相同的内存位置存储不同的数据类型。这意味着联合体的所有成员都共享同⼀块内存空间。(理解联合体
的本质是理解其大小计算的关键。)
179. (联合体大小的计算规则):
😀 • (联合体的大小等于其最大成员的大小):为了保证能够存储联合体中任何⼀个成员,联合体的大小必须足够容纳最大的成员。
• (联合体的大小必须是其内部所有成员的对齐模数(alignment modulus)的整数倍,也
即是对齐要求最高的成员大小的整数倍):这涉及到内存对齐,是为了提高CPU访问数据
的效率。
• (如果联合体包含结构体成员,则结构体成员也需要满足结构体对齐规则):结构体内部
的对齐会影响结构体的大小,从而影响联合体的大小。
180. (基本数据类型的大小):
😀 • char : 1 字节
• short : 2 字节
• int : 4 字节
• long : 在 32 位系统中通常是 4 字节,在 64 位系统中通常是 8 字节
• float : 4 字节
• double : 8 字节
• 指针类型:4 字节(32 位系统)或 8 字节(64 位系统)
181. (如何确定联合体的大小):
😀 1. (找到联合体中最大的成员):确定哪个成员占用最多的内存空间。
2. (考虑内存对齐):确保联合体的大小是其内部所有成员的对齐模数的整数倍,也即是对
齐要求最高的成员大小的整数倍。
182. (联合体大小计算示例):
#include <stdio.h>
union Test
{
char a; // 1 byte
int b; // 4 bytes
double c; // 8 bytes
};
int main()
{
printf("Size of union Test: %lu\n", sizeof(union Test)); // 输出 8
return 0;
}
😀 • (分析过程):联合体 Test 中,最大的成员是 double c ,占用 8 字节。因此,联
合体 Test 的大小为 8 字节。
183. (包含结构体的联合体大小计算示例):
#include <stdio.h>
struct Inner
{
char x; // 1 byte
short y; // 2 bytes
}; // Size: 4 (1 + 1 padding + 2)
union Outer
{
int a; // 4 bytes
struct Inner b; // 4 bytes
double c; // 8 bytes
};
int main()
{
printf("Size of union Outer: %lu\n", sizeof(union Outer)); // 输出 8
return 0;
}
😀 • (分析过程):联合体 Outer 中,最大的成员是 double c ,占用 8 字节。 struct Inner 的大小为 4 字节(需要考虑结构体对齐),小于 double 的大小。 因此,联合体 Outer 的大小为 8 字节。
184. (联合体与内存对齐):
• (联合体的大小需要满足其内部所有成员的对齐要求):这意味着联合体的大小必须是其对齐要求最高的成员大小的整数倍。
• (示例):
#include <stdio.h>
union AlignTest
{
char a; // 1 byte
double b; // 8 bytes
};
int main()
{
printf("Size of union AlignTest: %lu\n", sizeof(union AlignTest)); // 输出 8
return 0;
}
😀 • (分析过程):即使 char a 只占用 1 字节,但由于 double b 需要 8 字节对齐,因此联合体 AlignTest 的大小为 8 字节。
185. (使用 #pragma pack 对联合体大小的影响):
• ( #pragma pack(n) 可以影响联合体内部结构体成员的对齐方式):但通常不会直接影响联合
体本身的大小,因为联合体的大小取决于其最大成员的大小。
• (示例):
#include <stdio.h>
#pragma pack(1)
struct PackedInner
{
char x; // 1 byte
short y; // 2 bytes
}; // Size: 3
#pragma pack()
union PackedOuter
{
int a; // 4 bytes
struct PackedInner b; // 3 bytes
double c; // 8 bytes
};
int main()
{
printf("Size of union PackedOuter: %lu\n", sizeof(union PackedOuter)); //输出 8
return 0;
}
😀 • (分析过程):即使 struct PackedInner 的大小被 #pragma pack(1) 影响, 减小到 3 字节,但由于联合体中最大的成员 double c 仍然占用 8 字节,因此联合体PackedOuter 的大小仍然为 8 字节。
186. (联合体的应用场景):
😀 • (节省内存空间):当多个变量互斥时,可以使用联合体来共享同一块内存空间。
• (类型转换):可以使用联合体来实现不同数据类型之间的转换。
• (底层数据表示):可以使用联合体来查看数据的底层表示。
187. (需要注意的问题):
😀 • (在访问联合体成员时,需要确保访问的是当前存储在联合体中的成员):否则可能会导致错误的结果。
• (联合体的大小只取决于其最大成员的大小,与成员的赋值顺序无关):无论先给哪个成
员赋值,联合体的大小都不会改变。