自定义类型之结构体,枚举,联合(下)

简介: 自定义类型之结构体,枚举,联合

为什么要内存对齐?


讲了这么久的内存对齐,那我们不好奇为啥要内存对齐导致浪费那么多空间呢?这样设计的意义是什么?


查阅资料是这么说的:


  1. 平台原因(移植原因):


不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常.(这个我牛牛见识少,没咋遇见过,不过可以理解)


  1. 性能原因:


数据结构(尤其是栈)应该尽可能地在自然边界上对齐。


原因在于为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问.


举例:16位机器一次只能读取四个字节的数据.



总结:内存对齐就是一种以时间换空间的方法,不要小看一次和两次的区别,在大量数据读取时,作用是很大的.


1.4 查看偏移量与修改默认对齐数


在c语言中,有一种宏定义,可以帮助我们查看结构体中成员的偏移量.



这里我们只需要了解它是如何使用的就行.


#include <stdio.h>
#include <stddef.h>
typedef struct S2
{
  char c1;//0
  char c2;//1
  int i;//4
}s2;
typedef struct S3
{
  int a;//0-3
  char c1;//4
  int i;//8-11
  double b;//16-23
}s3;
typedef struct S4
{
  char c1;//1
  struct S3 s3;//8-31
  int d;//32-35
}s4;
int main()
{
  printf("%d\n", offsetof(s2, c1));
  printf("%d\n", offsetof(s2,c2));
  printf("%d\n", offsetof(s2, i));
  printf("\n%d\n", offsetof(s3, a));
  printf("%d\n", offsetof(s3, c1));
  printf("%d\n", offsetof(s3, i));
  printf("%d\n", offsetof(s3, b));
  printf("\n%d\n", offsetof(s4, c1));
  printf("%d\n", offsetof(s4, s3));
  printf("%d\n", offsetof(s4, d));
  return 0;
}


运行结果:


0
1
4
0
4
8
16
0
8
32


在vs环境下修改默认对齐数:


#pragma pack(4)//设置默认对齐数为4


在vs环境下还原默认对齐数:


#pragma pack()//取消设置的默认对齐数,还原为默认


为什么要修改默认对齐数?


我们知道内存对齐是以空间换时间的方法.


大多数情况下都是这样的,但是有的特殊时候,空间很有限,这时我们不想牺牲空间,即以时间换空间.此时就要使用修改默认对齐数了,使其不对齐.


二、位段篇


你听过"位段"吗?不是段位哦,嘘~~偷偷告诉你,牛牛王者是最强王者段位哦!


位段其实和结构体很相似,我们可以先观察一个位段先.


#include <stdio.h>
typedef struct A
{
  int a : 2;
  int b : 5;
  int c : 10;
  int d : 15;
}A;
int main()
{
  printf("%d", sizeof(A));
  return 0;
}


位段长得与结构体很相似,但是他与"富哥"结构体不一样,位段就十分节省了,字节都是省着用,一个字节都要按比特位数着用!是出了名的"吝啬鬼".


位段的使用要求:


1.位段的成员必须是 int、unsigned int 或signed int 或者char类型


2.位段的成员名后边有一个冒号和一个数字。(冒号后面的数字不得超过前面类型的大小.)


位段的位就是指"比特位",一个字节占8比特位.


位段空间使用规则是:


1.先申请该类型所占的字节个数个的字节空间(如:int占四个字节,则先申请四个字节的空间)


2.从低位向高位使用":"(冒号)个比特位的空间.


3.如果字节不够了,就继续申请新的类型大小数目的字节空间.


比如示例1:


typedef struct A
{
  int a : 2;
  int b : 5;
  int c : 10;
  int d : 15;
}A;


由于int占四个字节,则先申请四个字节空间.


a:2,表示a申请占用2个比特位,


b:5,表示b申请占用5个比特位,



是的你没有听错,就是比特位,经过计算,2+5+10+15=32,四个字节刚好够用,那他真的是占四个字节吗?


是的,不相信的小伙伴,可以复制示例1运行一下,结果就是四个字节.



那超出四字节会怎样申请新的空间?


#include <stdio.h>
typedef struct B
{
  int a : 20;
  int b : 5;
  int c : 10;
  int d : 15;
}B;
int main()
{
  a=1;
  b=2;
  c=3;
  d=5;
  printf("%d", sizeof(B));
  return 0;
}


分析:


int占四个字节,前申请四个字节的空间.


a占用了其中的20个比特位,b又占用了5个比特位.(还剩7个比特位)


c需要10个比特位,显然是不够的,


这时,又向内存申请了四个字节的空间,


假设,我们大方一点,这7个比特位不要了,直接使用新的内存空间,d又占用15个比特位,八个字节,足够了.


通过调试,我们发现,确实占用了八个字节大小.



我们可以通过调试,在内存窗口观察内存中实际是怎样存放的.



计算转化为16进制:



调试验证:(图中是小端存储,所以是反过来的).



但是,我们只是假设那7个剩余的比特位不要了,有的编译器可没有这么大方,这取决于编译环境,所以这也是位段的一个缺点!


位段总结:


  1. 在位段中,int是有符号还是无符号是未知的.


  1. 虽然说位段中":"(冒号)后面的数字不得超过该成员类型所占字节数所换算的比特位,但是在不同的平台,类型的大小是不确定的.


  1. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。(我们在vs是从左向右分配的)


  1. 当第一个位段的剩余的内容无法存储第二个位段时,要开辟新的空间,那之前剩余的空间是否被利用取决于平台,也没有规定.


总结,虽然位段节省了大量的空间,但是时间效率,跨平台可移植性都堪忧.


三.枚举(enum)


在c语言初阶时,我们在讨论c语言类型时,其实也提到过这个名词"枚举".


那么今天就来真正学懂"枚举"吧!


"枚举"其实就是列举的意思.


比如:


三原色: “红”,“绿”,“蓝”.


性别: “男”,“女”.


星期:" 星期一",“星期二”……


当一件事物可以一 一列举出来,我们可以使用枚举将他们表示出来.


枚举类型中的成员只有在定义时可以更改(因为常量也要有值不是吗?)


他们都是常量,定义之后是不允许更改的.


枚举的定义:


🌰栗子


#include <stdio.h>
enum Day//星期
{
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday
};
enum Sex//性别
{
  MALE=6,
  FEMALE,
};
enum Color//颜色
{
  RED,
  GREEN=7,
  BLUE=10
};
int main()
{
  printf("Day;");
  printf("%d ", Monday);
  printf("%d ", Tuesday);
  printf("%d ", Wednesday);
  printf("%d ", Thursday);
  printf("%d ", Friday);
  printf("%d ", Saturday);
  printf("%d ", Sunday);
  printf("\n");
  printf("%d ", MALE);
  printf("%d ", FEMALE);
  printf("\n");
  printf("%d ", RED);
  printf("%d ", GREEN);
  printf("%d ", BLUE);
  return 0;
}


运行结果:


Day;0 1 2 3 4 5 6


Sex;6 7


Color;0 7 10’’


对于未被初始化的枚举常量,默认是从0开始的.


枚举的优点


当我们在使用case语句,或者其它选择语句时,数字1,2,3这类并没有指向性.


这时我们可以用枚举常量代替他们,使得代码可读性极大提高.


示例:


请设计一个程序,要求:


输入:一个整数date范围是(0-7)


输出:打印星期一到星期天.


enum Day//星期
{
  Monday=1,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday
};
#include <stdio.h>
int main()
{
  int date = 0;
  scanf("%d", &date);
  switch (date)
  {
    case Monday:
      printf("星期一");
      break;
    case Tuesday:
      printf("星期二");
      break;
    case Wednesday:
      printf("星期三");
      break;
    case Thursday:
      printf("星期四");
      break;
    case Friday:
      printf("星期五");
      break;
    case Saturday:
      printf("星期六");
      break;
    case Sunday:
      printf("星期天");
      break;
    default:
      printf("超出范围了兄弟!!!\n");
  }
  return 0;
}


这个例子可能有些牵强,但是希望友友们能理解,很多情况,枚举常量还是能够可以帮助我们更好的提升代码可读性的.例如,通讯录那里有好的例子.


  1. 增加代码的可读性和可维护性


  1. #define定义的标识符比较枚举有类型检查,更加严谨。


  1. 防止了命名污染(封装)


  1. 便于调试


  1. 使用方便,一次可以定义多个常量


四、联合体(unio)


联合体是一种很特殊的自定义类型,他与结构体一样可以同时定义多个变量.


但是最主要的特点是,这些成员变量使用的都是同一块内存空间.


所以,他还有另外一个名字—共用体.


我们看一个联合体的例子.


union Un
{
  char a;
  int b;
  double c;
};


联合体的定义有了前面结构体的基础,还是很简单理解的.牛牛不过多介绍了.


联合体的应用:


#include <stdio.h>
typedef union test
{
  int a;
  char b;
}test;
int main()
{
  test t1;
  printf("%p\n", &(t1.a));
  printf("%p\n", &(t1.b));
  t1.a = 0xaabbccdd;
  t1.b = 0xaa;
  printf("%x\n", t1.a);
  return 0;
}


运行结果:


0000004B5DFCFC64


0000004B5DFCFC64


aabbccaa


联合体的特点是其中的成员变量都在使用同一块内存空间,所以打印出来的地址是一样的.


但是这样就产生了一个问题,如果我们同时使用这里的多个成员,那内存地址中存放谁的值呢?

所以联合体中的成员变量不能同时使用.


这也就是为什么修改了b,导致a的一个字节的数据也被修改的原因.



联合体的大小计算


很明显,由于他们都是使用同一块空间,所以大小是由最大成员变量所决定的,但是要注意的是,联合体也是要讲究内存对齐的.


练习一下吧!


#include <stdio.h>
union test1
{
  char a[9];
  int b;
  double c;
};
union test2
{
  short a[7];
  int b;
  char c;
};
int main()
{
  printf("%d\n", sizeof(union test1));
  printf("%d\n", sizeof(union test2));
  return 0;
}


答案:


16


16


分析:


test1:最大元素是a[9],但是需要内存对齐,对齐数是取double的8,所以占16字节.


test2:最大元素是a[7],占14个字节,但是对齐数是四个字节的b,所以也要内存对齐为16字节.

目录
相关文章
|
8天前
|
云安全 人工智能 算法
以“AI对抗AI”,阿里云验证码进入2.0时代
三层立体防护,用大模型打赢人机攻防战
1401 10
|
8天前
|
机器学习/深度学习 安全 API
MAI-UI 开源:通用 GUI 智能体基座登顶 SOTA!
MAI-UI是通义实验室推出的全尺寸GUI智能体基座模型,原生集成用户交互、MCP工具调用与端云协同能力。支持跨App操作、模糊语义理解与主动提问澄清,通过大规模在线强化学习实现复杂任务自动化,在出行、办公等高频场景中表现卓越,已登顶ScreenSpot-Pro、MobileWorld等多项SOTA评测。
1268 6
|
9天前
|
人工智能 Rust 运维
这个神器让你白嫖ClaudeOpus 4.5,Gemini 3!还能接Claude Code等任意平台
加我进AI讨论学习群,公众号右下角“联系方式”文末有老金的 开源知识库地址·全免费
1117 14
|
3天前
|
人工智能 前端开发 API
Google发布50页AI Agent白皮书,老金帮你提炼10个核心要点
老金分享Google最新AI Agent指南:让AI从“动嘴”到“动手”。Agent=大脑(模型)+手(工具)+协调系统,可自主完成任务。通过ReAct模式、多Agent协作与RAG等技术,实现真正自动化。入门推荐LangChain,文末附开源知识库链接。
399 118
|
6天前
|
存储 缓存 NoSQL
阿里云经济型e实例(ecs.e-c1m4.large)2核8G云服务器优惠活动价格及性能测评
阿里云经济型e实例(ecs.e-c1m4.large)2核8G配置,支持按使用流量或按固定带宽两种公网计费方式,搭配20G起ESSD Entry云盘,是主打高性价比的内存优化型入门选择。其核心特点是8G大内存适配轻量内存密集场景,计费模式灵活可控,既能满足个人开发者的复杂测试项目需求,也能支撑小微企业的基础业务运行,无需为闲置资源过度付费。以下从优惠活动价格、性能表现、适用场景及避坑要点四方面,用通俗语言详细解析。
224 153
|
4天前
|
机器学习/深度学习 人工智能 算法
炎鹊「Nexus Agent V1.0」:垂直领域AI应用的原生能力引擎
炎鹊AI「Nexus Agent V1.0」是垂直行业专属AI原生引擎,融合大模型、AIGA决策大脑、行业知识图谱与专属模型,打造“感知-决策-执行”闭环。支持21个行业低代码构建工具型、员工型、决策型AI应用,实现技术到业务价值的高效转化,推动AI从实验走向规模化落地。(239字)
243 1

热门文章

最新文章