C语言练习题集:联合体、结构体、枚举、内存对齐和位段的概念和应用

C语言基础练习题

简介

本篇博客将通过一系列练习题,深入探讨C语言中联合体、结构体、枚举、内存对齐和位段的概念和应用

通过解答这些练习题,读者将能够加深对这些关键概念的理解,并提升在C语言编程中的技能

鉴于水平有限 , 对于本篇中可能出现的一些错误 , 还请指正

第一题

下面代码的结果是:( )

  •   #include <stdio.h>
      union Un
      {
      	short s[7];
      	int n;
      };
      int main()
      {
        printf("%d\n", sizeof(union Un));
        return 0;
      }
    

    A.14

    B.4

    C.16

    D.18

    c

    解析:
    结构体向int对齐,7个short一共是14字节,对齐后是16字节

    n是单独的4字节,由于是union,所以n与s共用空间,只取最长的元素,故占用16字节

    联合体也有结构体的内存对齐,是为了保证联合体内存布局的一致性,以提高内存访问的效率

    联合(共用体):

    联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)

    //联合类型的声明
    union Un
    {
     char c;
     int i;
    };
    //联合变量的定义
    union Un un;
    //计算连个变量的大小
    printf("%d\n", sizeof(un));
    

    联合的特点:

    联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员

    union Un
    {
     int i;
     char c;
    };
    union Un un;
    // 下面输出的结果是一样的吗?
    printf("%d\n", &(un.i));
    printf("%d\n", &(un.c));
    //下面输出的结果是什么?
    un.i = 0x11223344;
    un.c = 0x55;
    printf("%x\n", un.i);
    printf("%x\n", un.c);
    

    在这里插入图片描述

    联合大小的计算:

    • 联合的大小至少是最大成员的大小
    • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
        union Un1
        {
            char c[5];
            int i;
        };
        union Un2
        {
            short c[7];
            int i;
        };
        //下面输出的结果是什么?
        printf("%d\n", sizeof(union Un1));
        printf("%d\n", sizeof(union Un2));
    
    

    8 , 16

    • 结构体向int对齐,5个char一共是5字节,对齐后8字节

      i是单独的4字节,由于是union,所以i与c共用空间,只取最长的元素,故占用8字节

    • 结构体向int对齐,7个short一共是14字节,对齐后16字节

      i是单独的4字节,由于是union,所以i与c共用空间,只取最长的元素,故占用16字节

    结构体内存对齐规则:

    • 第一个成员在与结构体变量偏移量为0的地址处
    • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
    • 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值
    • VS中默认的值为8
    • 结构体总大小最大对齐数每个成员变量都有一个对齐数)的整数倍
    • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

    为什么存在内存对齐?

    • 平台原因(移植原因):
      • 不是所有的硬件平台都能访问任意地址上的任意数据的
      • 某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常
    • . 性能原因:
      • 数据结构(尤其是栈)应该尽可能地在自然边界上对齐
      • 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问
      • 而对齐的内存访问仅需要一次访问

    总体来说:

    • 结构体的内存对齐是拿空间来换取时间的做法

    例如:

    int main()
    {
    	struct S1
    	{
    		char c1;
    		int i;
    		char c2;
    	};
    	struct S2
    	{
    		char c1;
    		char c2;
    		int i;
    	};
    	printf("%zd\n", sizeof(struct S1));
    	printf("%zd\n", sizeof(struct S2));
    }
    

    运行结果:

    12

    8

    两个结构体都是向int看齐 , 因为在本例中int最长

    结构体S1中,c1 + i超过了4字节,所以c1独自对齐一个4字节,i独自对齐一个4字节,剩下一个c2独自对齐一个4字节,共12字节

    结构体S2中,c1 + c2 +i才超过了4字节,所以c1c2一起对齐一个4字节,i单独对齐一个4字节,共8字节


第二题

VS2013下,默认对齐数为8字节,这个结构体所占的空间大小是( )字节

  •   typedef struct{
        int a;
        char b;
        short c;
        short d;
      }AA_t;
    

    A.16

    B.9

    C.12

    D.8

    C

    与第一题做法类似


第三题

下面代码的结果是:( )

  •   #pragma pack(4)/*编译选项,表示4字节对齐 平台:VS2013。语言:C语言*/
      //假设long 是4个字节
      int main(int argc, char* argv[])
      {
        struct tagTest1
        {
          short a;
          char d; 
          long b;   
          long c;   
        };
        struct tagTest2
        {
          long b;   
          short c;
          char d;
          long a;   
        };
        struct tagTest3
        {
          short c;
          long b;
          char d;   
          long a;   
        };
        struct tagTest1 stT1;
        struct tagTest2 stT2;
        struct tagTest3 stT3;
      
        printf("%d %d %d", sizeof(stT1), sizeof(stT2), sizeof(stT3));
        return 0;
      }
      #pragma pack()
    

    A.12 12 16

    B.11 11 11

    C.12 11 16

    D.11 11 16

    A

    解析:

    因为假设long 是4个字节 , 三个结构体都向最长的4字节long看齐
    第一个a+d+b才超过4字节,所以a和d一起对齐一个4字节,剩下两人独自占用,共12字节

    第二个同理c,d合起来对齐一个四字节,也是12字节

    第三个因为c+b,d+a都超过4字节了,所以各自对齐一个4字节,共16字节


第四题

有如下宏定义和结构定义

  •   #define MAX_SIZE A+B
      struct _Record_Struct
      {
        unsigned char Env_Alarm_ID : 4;
        unsigned char Para1 : 2;
        unsigned char state;
        unsigned char avail : 1;
      }*Env_Alarm_Record;
      struct _Record_Struct *pointer = (struct _Record_Struct*)malloc(sizeof(struct _Record_Struct) * MAX_SIZE);
    

    当A=2, B=3时,pointer分配( )个字节的空间

    A.20

    B.15

    C.11

    D.9

    D

    解析:

    结构体向最长的char对齐,前两个位段元素一共4+2位,不足8位,合起来占1字节

    state单独占一行

    最后一个单独1字节,一共3字节

    另外,#define执行的是查找替换, sizeof(struct _Record_Struct) * MAX_SIZE这个语句其实是3*2+3,结果为9

    位段的内存分配:

    • 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型

    • 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的

    • 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段

    //例子
    struct S
    {
     char a:3;
     char b:4;
     char c:5;
     char d:4;
    };
    struct S s = {0};
    s.a = 10;
    s.b = 12;
    s.c = 3;
    s.d = 4;
    

    在这里插入图片描述

    位段的跨平台问题:

    • int 位段被当成有符号数还是无符号数是不确定的
    • 位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)
    • 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义
    • 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的

    总的来说 : 跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在


第五题

X86下,小端字节序存储,有下列程序

  •   #include<stdio.h>
      int main()
      {
        union
        {
          short k;
          char i[2];
        }*s, a;
        s = &a;
        s->i[0] = 0x39;
        s->i[1] = 0x38;
        printf("%x\n", a.k);
        return 0;
      }
    

    输出结果是( )

    A.3839

    B.3938

    C.380039

    D.不确定

    A

    解析:

    union只有2字节,2字节的十六进制只有4位,所以答案C , D排除

    而位顺序类似小端,低地址在低处,所以39是低地址,在低位,38在高位,所以是3839

    在这里插入图片描述

    小端存储与大端存储:

    • 在小端存储中,最低有效字节(Least Significant Byte,LSB)存储在最低地址,而最高有效字节(Most Significant Byte,MSB)存储在最高地址

    • 这种存储方式与人类的阅读习惯相符,因为我们从左到右读取字节时,先读取低位字节,再读取高位字节

    • 举个例子

      对于一个32位整数0x12345678,在小端存储中,它在内存中的存储方式如下:

      低地址 0x78 0x56 0x34 高地址 0x12

      可以看到,最低有效字节0x78存放在最低地址,最高有效字节0x12存放在最高地址

      对于同样的32位整数0x12345678,在大端存储中,它在内存中的存储方式如下:

      低地址 0x12 0x34 0x56 高地址 0x78

      可以看到,最高有效字节``0x12存放在最低地址,最低有效字节0x78`存放在最高地址

    不管是小端存储还是大端存储谈的是字节的存储顺序 , 是以字节为最小单位 , 与字节内部的排列方式没有关系


第六题

下面代码的结果是:( )

  •   enum ENUM_A
      {
        X1,
        Y1,
        Z1 = 255,
        A1,
        B1,
      };
      enum ENUM_A enumA = Y1;
      enum ENUM_A enumB = B1;
      printf("%d %d\n", enumA, enumB);
    

    A.1, 4

    B.1, 257

    C.2, 257

    D.2, 5

    B

    解析:

    枚举默认从0开始,所以X1是0,故Y1是1,给了数字后会根据数字向后推,那么Z1是255,A1是256,所以B1是257

    枚举:

    枚举顾名思义就是一一列举

    把可能的取值一一列举

    比如 :

    • 一周的星期一到星期日是有限的7天,可以一一列举
    • 性别有:男、女、保密,也可以一一列举
    • 月份有12个月,也可以一一列举

    枚举类型的定义:

    enum Day//星期
    {
     Mon,
     Tues,
     Wed,
     Thur,
     Fri,
     Sat,
     Sun
    };
    enum Sex//性别
    {
     MALE,
     FEMALE,
     SECRET
    };
    enum Color//颜色
    {
     RED,
     GREEN,
     BLUE
    };
    

    以上定义的 enum Day enum Sexenum Color 都是枚举类型

    {}中的内容是枚举类型的可能取值,也叫 枚举常量

    我们可以使用 ``#define` 定义常量,为什么非要使用枚举?

    枚举的优点:

    • 增加代码的可读性和可维护性
    • 和#define定义的标识符比较枚举有类型检查,更加严谨
    • 防止了命名污染(封装
    • 便于调试
    • 使用方便,一次可以定义多个常量

    枚举的使用:

    enum Color//颜色
    {
     RED=1,
     GREEN=2,
     BLUE=4
    };
    enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异
    clr = 5; 
    

第七题

下面代码的结果是( )

  •   int main()
      {
        unsigned char puc[4];
        struct tagPIM
        {
          unsigned char ucPim1;
          unsigned char ucData0 : 1;
          unsigned char ucData1 : 2;
          unsigned char ucData2 : 3;
        }*pstPimData;
        pstPimData = (struct tagPIM*)puc;
        memset(puc,0,4);
        pstPimData->ucPim1 = 2; 
        pstPimData->ucData0 = 3;
        pstPimData->ucData1 = 4;
        pstPimData->ucData2 = 5;
        printf("%02x %02x %02x %02x\n",puc[0], puc[1], puc[2], puc[3]);
        return 0;
      }
    

    A.02 03 04 05

    B.02 29 00 00

    C.02 25 00 00

    D.02 29 04 00

    B

    解析:
    puc是一个char数组,每次跳转一个字节,结构体不是,它只有第一个元素单独享用一字节,其他三个元素一起共用一字节,所以puc被结构体填充后,本身只有两个字节会被写入,后两个字节肯定是0,至此AD排除,然后第一个字节是2就是2了,第二个字节比较麻烦,首先ucData0给了3其实是越界了,1位的数字只能是0或1,所以11截断后只有1,同理ucData1给的4也是越界的,100截断后是00,只有5的101是正常的。填充序列是类似小端的低地址在低位,所以排列顺序是00 101 00 1。也就是0010 1001,即0x29


第八题

在32位系统环境,编译选项为4字节对齐,那么sizeof(A)sizeof(B)是( )

  •   struct A
      {
      	int a;
      	short b;
      	int c;
      	char d;
      };
      
      struct B
      {
      	int a;
      	short b;
      	char c;
      	int d;
      };
    

    A.16,16

    B.13,12

    C.16,12

    D.11,16

    C

    解析:

    两个结构体都是向int看齐

    结构体A中,a独自对齐一个4字节,b+c超过了4字节,所以b独自对齐一个4字节,c独自对齐一个4字节,剩下一个d独自对齐一个4字节,共16字节

    结构体B中,a独自对齐一个四字节,b+c+d才超过了4字节,所以b和c一起对齐一个4字节,d单独对齐一个4字节,共12字节

结语

通过本篇的练习题,我们对C语言中的联合体、结构体、枚举、内存对齐和位段有了进一步的理解

这些概念在C语言编程中起着重要的作用,掌握它们将使我们能够更加灵活和高效地处理数据

希望本文能对各位有所帮助!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁星ベ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值