0%

C语言结构体和联合体

编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析、语法分析、语义检查和中间代码生成、代码优化、目标代码生成。

一、结构体struct

      结构体是由一批数据组合而成的一种新的数据类型,组成结构型数据的每个数据称为结构型数据的成员,这些成员可以具有不同的数据类型。由于结构体数据的成员类型不同,因此结构体要由用户在程序中自己定义。它是一种构造类型,是由若干成员组成的。每一个成员可以是一个基本数据类型(整型、字符型、浮点型),也可以是一个构造类型(结构体、共用体、数组)。

      结构体本身只是用户定义的一个数据类型,不占用什么存储空间,定义结构体变量以后,系统为其分配一定的存储空间,它在内存中是一个连续的内存单元,总字节数等于该结构型的所有成员所占用的字节数之和。

  1. 定义一个结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
struct unknown {
  int a;
  char b;
  int c;
  long d;
  char e;
};

int main(int argc, char** argv) {
printf("%lu\n", sizeof(struct unknown)); //output:32
return 0;
}
  1. 计算内存大小

    • 每个成员的偏移量都必须是当前成员所占内存大小的整数倍,如果不是编译器会在成员之间加上填充字节
    • 当所有成员大小计算完毕后,编译器判断当前结构体大小是否是结构体中最宽的成员变量大小的整数倍,如果不是会在最后一个成员后做字节填充
  2. 针对上面的结构体进行计算

    • 成员变量a,偏移量为0,大小为4字节,符合偏移规则第一条
    • 成员变量b,偏移量为4,大小为1字节,符合偏移规则第一条
    • 成员变量c,偏移量为5,大小为4字节,不符合偏移规则第一条
      • 于是在成员b和c间填充三个字节,此时偏移量大小为8,符合偏移规则第一条,继续往下
    • 成员变量d,偏移量为12,大小为8字节,不符合偏移规则第一条
      • 于是在成员c和d之间填充4个字节,此时偏移量为16,符合偏移规则第一条,继续往下
    • 成员变量e,偏移量为24,大小为1字节,符合偏移规则第一条
    • 所有成员大小计算完后,执行偏移规则第二条,最宽的成员是long d占8个字节,当前结构体计算出的大小为25,不符合第二条规则
      • 于是在末尾填充7个字节,总大小为32个字节,满足第二条偏移规则。
    • 到此结构体大小计算结束
  3. 常用于产生柔性数组,即数组大小待定的数组

1
2
3
4
struct SoftArray {
int len;
int array[]; // 仅仅为一个标识符,不占用存储空间
};
  1. 几种方式

    • 定义结构体标准语法:

      • tag是结构体标签(名称)。
      • member-list是标准的变量定义,称为结构体成员。
      • variable-list是结构变量,定义在结构的末尾,最后一个分号之前。
      1
      2
      3
      4
      5
      6
      struct tag { 
      member-list
      member-list
      member-list
      ...
      } variable-list;
    • 在一般情况下,tag、member-list、variable-list这3部分至少要出现2个,因此也就出现了多种声明方式。

      • 第一种:基本定义
      1
      2
      3
      struct A {
      int a;
      };
      • 第二种:结构体的同时定义了一个结构体变量s1。
      1
      2
      3
      struct A {
      int a;
      }s1;
      • 第三种:没有结构体名称但定义了结构体变量n。
        • 若是想要在别处定义该结构体的变量是不行的,只有变量n这种在定义结构体的同时定义变量才行。
      1
      2
      3
      struct{
      int a;
      }n;
      • 第四种:使用关键字typedef。
        • struct D{int d}看成是一个数据类型,作为一个整体,使用时需用struct D test;格式
      1
      2
      3
      typedef struct D {
      int d;
      };
      • 第五种:在第四种结构体定义的基础上加上了别名x。
        • 使用时可用x test;struct E test;格式
      1
      2
      3
      typedef struct E {
      int e;
      }x;
      • 第六种:在第五种的基础上减去了结构体名。
        • 使用时可用y test;格式
      1
      2
      3
      typedef struct {
      int f;
      }y;

二、共同体union

  1. 定义一个结构体
1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
union uni {
int a;
char b;
long c;
int d[4];
};

int main(int argc, char** argv) {
printf("%lu\n", sizeof(union uni)); //output:16
return 0;
}
  1. 计算内存大小

    • union的大小就是最大的成员所占空间大小
  2. 针对上面的结构体进行计算

    • int a占4个字节
    • char b占1个字节
    • long c占8个字节
    • int d[4]占16个字节
    • 故union大小为16字节
  3. 大小端问题

    • 小端模式下,低地址存储低位数据
    • 大端模式下,高地址存储低位数据
  4. 成员赋值问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main(int argc, char** argv) {
union
{
short i;
char a;
}uni;

uni.i = 0x4241;
printf("%x\n", uni.i); // 4241

uni.a = 'a';
printf("%x\n", uni.i); // 4261

return 0;
}

三、扩展

  1. struct和union对比

    • 结构体各个成员占用不同的内存块,互相之间没有影响;联合体所有成员共用同一段内存块,修改其中一个成员会对整个内存块保存的值产生影响。
      • 对于union的不同成员赋值,将会对其他成员重写,即原来的成员值就不存在了,而对struct的不同成员赋值是互不影响的。
    • size大小
      • union只分配最大成员的空间,所有成员共享这个空间,故union的大小就是最大的成员所占空间
      • struct中的每个数据成员有独立的存储空间,struct在分配空间时要遵循内存对齐规则,即根据成员类型不同会存在字节对齐情况,具体对齐标准和机器有关
  2. 内存对齐

    • 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k的倍数,这就是所谓的内存对齐,通俗的数就是就是编译器为程序中的每个数据单元安排在适当的位置上。

      • C语言的一个特点就是太灵活,太强大,它允许你干预内存对齐,可以通过预编译命令#pragma pack(8)实现修改对齐系数,默认为4。
    • 尽管内存的分配是以字节byte为单位,但是大部分编译器并不是按字节来管理内存的,一般会以2字节、4字节、8字节、16字节为单位来存取内存,进而管理效率。

    • 对齐规则具体如下:

      • 对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员偏移量必须是当前成员类型和对其参数的最小整数倍
      • 结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,整个结构体占用内存大小是结构体内最大数据成员的最小整数倍
      • 如程序中有#pragma pack(n)预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型
    • 官方版本:

      • 基本类型的对齐值就是其sizeof值;
      • 数据成员对齐规则:结构体或联合体的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中比较小的那个进行;
      • 整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中比较小的那个进行。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <stddef.h>

      typedef struct Test {
      long double b;
      int a;
      char c[19];
      } Test;

      int main() {
      Test t;
      printf("t.b的偏移量为:%d\n", offsetof(struct Test, b));
      printf("t.a的偏移量为:%d\n", offsetof(struct Test, a));
      printf("t.c的偏移量为:%d\n", offsetof(struct Test, c));

      printf("t的内存大小为:%lu\n", sizeof(Test));

      return 0;
      }
    • demo

      • struct.c
        • 运行gcc struct.c -o struct && ./struct
        • 得到结果32,分析如下:
          • 第一个成员int c,偏移量为0,占4字节
          • 第二个成员double d,偏移量为4,占8个字节,此时实际对齐参数已变为8,需要对第一个成员c进行补齐,实际偏移量变为8
          • 第三个成员char b[10],偏移量为16,占10个字节,至此数据成员对齐完成,此时总共占26个字节
          • 对结构体整体对齐,最大成员d的最小整数倍为32,故最终占32字节
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      #include <stdio.h>

      int main() {
      typedef struct a {
      int c;
      double d;
      char b[10];
      }test;

      test e;
      printf("%lu", sizeof(e));

      return 0;
      }
      • struct.c
        • 运行gcc struct.c -o struct && ./struct
        • 得到结果32,分析如下:
          • 第一个成员double d,偏移量为0,占8字节
          • 第二个成员int c,偏移量为8,占4个字节,实际对齐参数为4,此时共占12个字节
          • 第三个成员char b[10],偏移量为12满足条件,占10个字节,至此数据成员对齐完成,此时总共占22个字节
          • 对结构体整体对齐,最大成员d的最小整数倍为24,故最终占24字节
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      #include <stdio.h>

      int main() {
      typedef struct a {
      double d;
      int c;
      char b[10];
      }test;

      test e;
      printf("%lu", sizeof(e));

      return 0;
      }
      • 使用pragma pack

        • pack(8)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        #include <stdio.h>
        #pragma pack(8)

        int main() {
        struct Test {
        int a;
        long double b;
        char c[10];
        };

        printf("%lu", sizeof(struct Test));

        return 0;
        }
        • pack(16)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        #include <stdio.h>
        #pragma pack(16)
        int main() {
        struct Test {
        int a;
        long double b;
        char c[10];
        };

        printf("%lu", sizeof(struct Test));

        return 0;
        }
        • pack(1)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        #include <stdio.h>
        #pragma pack(1)
        int main() {
        struct Test {
        int a;
        long double b;
        char c[11];
        };

        printf("%lu", sizeof(struct Test));

        return 0;
        }
        • pack(2)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        #include <stdio.h>
        #pragma pack(2)
        int main() {
        struct Test {
        int a;
        long double b;
        char c[11];
        };

        printf("%lu", sizeof(struct Test));

        return 0;
        }
      • 成员顺序

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      #include <stdio.h>
      struct A {
      int a;
      double b;
      char c;
      };
      struct B {
      double b;
      char c;
      int a;
      };
      struct C {
      int a;
      char c;
      double b;
      };

      int main(void) {
      struct A aa;
      struct B bb;
      struct C cc;
      printf("A = %d\n", sizeof(aa));//结果:A = 24
      printf("B = %d\n", sizeof(bb));//结果:B = 16
      printf("C = %d\n", sizeof(cc));//结果:C = 16
      return 0;
      }
      • demo5
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      #include <stdio.h>
      #include <stddef.h>
      struct A {
      int a;
      char c;
      double b;
      };
      struct Test {
      int x;
      long double y;
      char z[11];
      };

      int main() {
      struct A aa;
      printf("成员a的偏移量:%d\n", offsetof(struct A, a));
      printf("成员c的偏移量:%d\n", offsetof(struct A, c));
      printf("成员b的偏移量:%d\n", offsetof(struct A, b));
      printf("aa总大小为:%d\n\n", sizeof(aa));

      printf("x的偏移量为:%d\n", offsetof(struct Test, x));
      printf("y的偏移量为:%d\n", offsetof(struct Test, y));
      printf("z的偏移量为:%d\n", offsetof(struct Test, z));
      printf("总大小为:%lu\n\n", sizeof(struct Test));

      int l = 4;
      long double m = 1.2;
      printf("l大小为:%lu\n", sizeof(l));
      printf("m大小为:%lu\n", sizeof(m));

      return 0;
      }
      • 在结构体声明语句前加上__attribute__ ((__packed__))关键字,可以让成员按照紧凑排列的方式占用内存,无需进行内存对齐。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>

      typedef struct test1 {
      char a;
      int b;
      } T1;

      typedef struct __attribute__ ((__packed__)) test2 {
      char c;
      int d;
      } T2;

      int main() {
      T1 t1 = {'a', 4};
      T2 t2 = {'b', 12};

      printf("t1的大小:%lu\n", sizeof(t1));
      printf("t2的大小:%lu\n", sizeof(t2));
      return 0;
      }

四、人员管理

  1. 代码man.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <stdlib.h>

union subject {
float score;
char course[20];
};

struct body {
char name[20];
int num;
char sex;
char profession;
union subject sc;
};

int main() {
int i;
int num;
printf("请输入人数:");
scanf("%d", &num);

struct body b[num];

for (i=0; i<num; i++) {
printf("请输入人员信息: ");
scanf("%s %d %c %c", b[i].name, &(b[i].num), &(b[i].sex), &(b[i].profession));
if (b[i].profession == 's') {
scanf("%f", &b[i].sc.score);
} else {
scanf("%s", b[i].sc.course);
}
fflush(stdin);
}

printf("\n姓名\t 序号\t 性别\t 职业\t 学科/分数\n");
for (i=0; i<num; i++) {
if (b[i].profession == 's') {
printf("%s \t %d \t %c \t %c \t %f \n", b[i].name, b[i].num, b[i].sex, b[i].profession, b[i].sc.score);
} else {
printf("%s \t %d \t %c \t %c \t %s \n", b[i].name, b[i].num, b[i].sex, b[i].profession, b[i].sc.course);
}
}

return 0;
}
  1. 运行 gcc man.c -o main && ./main

五、参考

  1. 参考一
  2. 参考二
  3. 参考三