C语言中自定义类型详解

目录
  • 结构大小
  • offsetof
  • 结构体对齐规则
  • 存在原因
  • 总结

结构大小

我们先随便给出一个结构体,为了计算他的大小,我给出完整的打印方案:

typedef struct num
{
	char c;
	int n;
	char cc;
}num;

int main()
{
	printf("%d\n", sizeof(num));
	return 0;
}

好了,按道理来说我计算一个结构体大小就看他的各个成员需要消耗多大的空间, num 结构体中三个成员分别是 char ,int ,char 类型,对应 1 , 4, 1 字节大小,这么说来只需要 6 字节空间就ok了;但是——我们看看打印结果:

你说整点小误差就算了,好家伙直接歪了两倍出来,why?要解释这个问题这就需要引入 offsetof

offsetof

嘛是 offsetof,本质上他是个宏,C 语言库宏 offsetof 会生成一个类型为 size_t 的整型常量(size_t是标准C库中定义的,在64位系统中为long long unsigned int),它是一个结构成员相对于结构开头的字节偏移量。声明为:

offsetof(type, member-designator)

其中结构体成员是由 member-designator 给定的,结构体的名称是在 type 中给定的,需要<stddef.h>头文件支持。

我们就来康康各个成员的偏移量究竟是多少

#include<stddef.h>
int main()
{
	printf("%d\n", offsetof(num,c));
	printf("%d\n", offsetof(num, n));
	printf("%d\n", offsetof(num, cc));
	return 0;
}

结果如下:

我们可以清楚的看到刚刚的 12 的组成是怎么来的了,我们知道偏移量单位是字节,我们的 0,4,8 三个偏移量单位也就是字节,num 在内存中开始存储的位置相对于第一个成员进去的位置偏移量为0,也就是在同一个位置,第一个字节偏移量为 0,第二个字节偏移量为 1,第三个字节偏移量为2,以此类推。

我们搞个图来具象一下这个过程(手残ppt)

c,cc 是一个字节的 char 类型,n 是四字节的 int 类型,所以实际上我们利用的空间就只有上面的有颜色部分。

那么新的问题又来了,为什么会有空白部分(偏移量为1,2,3和未画出的12)?空白部分又干什么去了? 那我们就要明白结构体的对齐规则。

结构体对齐规则

1. 结构体第一个成员存在于结构体偏移量为 0 的地址处,也就是同起点开始。
2.其他成员变量要对齐到某个数字(对齐数)整数倍的地址处。地址数等于编译器默认的一个对齐数与该成员大小的较小值(我所使用的编译器是 vs 2019,vs中默认的值为 8,但 Linux环境无默认对齐数,对齐数就是成员自身大小)
3. 结构体总大小是最大对齐数的整数倍
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍。

解释

第一条很好理解吧,我们第二条就拿成员 n 来说,n 的大小为 4编译器默认对齐数为 8,取 4,8 中的较小值 4 作为对齐数。

第三条的最大对齐数如何理解呢?其实就是所有成员中对齐数最大的那个,三个成员对齐数分别是 1,4,1 ,取最大值就是 4,要是 4 的整数倍才行,我们刚好取完最后一个成员 cc 对齐数是8,整个空间 0-8 大小就是 9,9不是4 的倍数我们扔掉,然后继续浪费掉三个空间直到来到我们的 12,满足条件跳出。

举个栗子:

struct num2
{
double d;
char c;
int n;
};

我们按照规则画出他的内存分布:

因为 double 类型是 8 个字节,作为最大的成员,对齐数就是 8,从 0-15 大小为 16,16 是 8 的整数倍,因此结构体大小就是 16。嵌套情况不赘述,和一般情况同理。

存在原因

==So,为什么结构体会存在这种对齐机制呢 ?==两个方面:

从移植性的角度:平台不一样功能不一样,非所以硬件平台都可以访问任意地址上的任意数据,某些硬件平台只能在特定地址上取得特定的数据类型,否则就会硬件异常

从性能的角度:数据结构尤其是栈这种,应该尽可能的从自然边界上对齐,原因就是为了访问内存,处理器需要对散序的空间作两次内存访问;而对齐的内存仅仅需要一次,也就是我们常用的手法:用空间换时间

如果说在结构对齐方式不合适的时候,我们能自己更改默认对齐数来提高性能吗? 当然可以!

#pragma pack(num)
{
……
return 0;
}
#pragma pack()

这个宏可以取消默认对齐数,括号里 num 设置成自己满意的对齐数,最后再用这个宏取消自己的设置即可。

今天就到这里吧,摸了家人们。

总结

到此这篇关于C语言中自定义类型详解的文章就介绍到这了,更多相关C语言自定义类型内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言自定义类型的保姆级讲解

    前言 在我们日常写代码时,经常会遇到结构体类型的使用,今天带读者了解结构体类型的使用. 一.初始结构体 在了解结构体之前,我们先来了解一下结构体的基础只是,结构体到底是什么? 结构是一些值的集合,这些值称为成员变量.结构的每个成员可以是不同类型的变量. 下面举一个例子: struct tag { menber_list; //成员列表 }variable_list; //变量列表 例如我们使用结构体描述一台电脑 struct computer { int price;//价格 char name

  • C语言自定义类型详解(结构体、枚举、联合体和位段)

    目录 前言 一.结构体 1.结构体类型的声明 2.结构体的自引用 3.结构体变量的定义和初始化 4.结构体内存对齐 5.结构体传参 二.位段 1.位段的定义 2.位段的内存分配 3.位段的应用 三.枚举 1.枚举类型的定义 2.枚举的优点 3.枚举的使用 四.联合体(共用体) 1.联合体的定义 2.联合体的特点 3.联合体的大小计算 总结 前言 一.结构体 1.结构体类型的声明 当我们想要描述一个复杂变量--学生,可以这样声明. ✒️代码展示: struct Stu { char name[20

  • C语言:自定义类型详解

    目录 一.结构体 1.结构体变量的定义及初始化 2.结构体内存对齐 3.为什么要内存对齐呢? 二.位段 1.什么是位段 2.位段的内存分配 三.枚举 1.枚举的定义 2.枚举的优点 四.联合(共用体) 1.联合类型的定义 2.联合的特点 3.联合大小的计算 总结 一.结构体 1.结构体变量的定义及初始化 直接上代码: struct Point { int x; int y; }p1; //创建结构体时顺便创建变量,分号一定不能掉 struct Point p2; //单独创建变量 struct

  • C语言中自定义类型详解

    目录 结构大小 offsetof 结构体对齐规则 存在原因 总结 结构大小 我们先随便给出一个结构体,为了计算他的大小,我给出完整的打印方案: typedef struct num { char c; int n; char cc; }num; int main() { printf("%d\n", sizeof(num)); return 0; } 好了,按道理来说我计算一个结构体大小就看他的各个成员需要消耗多大的空间, num 结构体中三个成员分别是 char ,int ,char

  • jsp中自定义Taglib详解

    一.自定义标签入门之无参数自定义标签 1.开发自定义标签类 当我们在JSP页面使用一个简单的标签时,底层实际上由标签处理类提供支持,从而可以使用简单的标签来封装复杂的功能,从而使团队更好地协作开发(能让美工人员更好地参与JSP页面的开发). 自定义标签类都必须继承一个父类:javax.servlet.jsp.tagext.SimpleTagSupport,或者TagSupport除此之外,JSP自定义标签类还有如下要求. 如果标签类包含属性,每个属性都有对应的getter和setter方法. 重

  • C语言中冒泡排序算法详解

    目录 一.算法描述 二.算法分析 三.完整代码 总结 一.算法描述 比较相邻两个元素,如果第一个比第二个大则交换两个值.遍历所有的元素,每一次都会将未排序序列中最大的元素放在后面.假设数组有 n 个元素,那么需要遍历 n - 1 次,因为剩下的一个元素一定是最小的,无需再遍历一次.因此需要两层循环,第一层是遍历次数,第二层是遍历未排序数组. 动图如下: 黄色部分表示已排好序的数组,蓝色部分表示未排序数组 核心代码如下: /** * @brief 冒泡排序 * * @param arr 待排序的数

  • Go语言中你不知道的Interface详解

    前言 最近在看Go语言的面向对象的知识点时,发现它的面向对象能力全靠 interface 撑着,而且它的 interface 还与我们以前知道的 interface 完全不同.故而整个过程不断的思考为什么要如此设计?这样设计给我们带来了什么影响? interface 我不懂你 Rob Pike 曾说: 如果只能选择一个Go语言的特 性移植到其他语言中,他会选择接口 被Go语言设计者如此看重,想来 interface 一定是资质不凡,颜值爆表.但是说实话,当我第一次读这部分内容的时候,我产生了以下

  • Python动态语言与鸭子类型详解

    今天来说说编程语言中的动态类型语言与鸭子类型. 动态语言 维基百科对动态语言的定义: 动态编程语言是一类在运行时可以改变其结构的语言:例如新的函数.对象.甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化.动态语言目前非常具有活力如PHP.Ruby.Python 都属于动态语言,而C.C++.Java等语言则不属于动态语言. 这个解释很抽象,其实动态语言是相对静态语言而言的,静态语言的特点是在程序执行前,代码编译时从代码中就可以知道一切,比如变量的类型,方法的返回值类型: String

  • Go语言中的函数详解

    1.函数的声明定义 //func关键字 //getStudent函数名 //(id int, classId int) 参数列表 //(name string,age int) 返回值列表 func getStudent(id int, classId int)(name string,age int) { //函数体 if id==1&&classId==1{ name = "BigOrange" age = 26 } //返回值 return name, age /

  • C语言中的常量详解

    目录 C语言中的常量 字面常量 #define定义的标识符常量 枚举常量 C语言中的常量 C编程中的常量是一些固定的值,它在整个程序运行过程中无法被改变. 字面常量 字面常量是直接写出的固定值,它包含C语言中可用的数据类型,可分为整型常量,字符常量等.如:9.9,"hello"等就属于这一类常量. ##const修饰的常变量 有的时候我们希望定义这么一种变量:值不能被修改,在整个作用域中都维持原值.为了满足用户需求,C语言标准提供了const关键字.在定义变量的同时,在变量名之前加上c

  • Go语言之嵌入类型详解

    一.什么是嵌入类型 先看如下代码: type user struct { name string email string } type admin struct { user // Embedded Type level string } 可以看到admin结构中的一个成员是user,那么admin中就嵌入了user类型. admin也叫做外部类型 user也叫做内部类型 二.外部类型和内部类型之间的关系和机制 func (u *user) notify() { fmt.Printf("Sen

  • Go语言中的闭包详解

    一.函数的变量作用域和可见性 1.全局变量在main函数执行之前初始化,全局可见 2.局部变量在函数内部或者if.for等语句块有效,使用之后外部不可见 3.全局变量和局部变量同名的情况下,局部变量生效. 4.可见性: 包内任何变量或函数都是能访问的. 包外的话,首字母大写是可以访问的,首字母小写的表示私有的不能被外部调用. 二.匿名函数 1.Go语言中函数也是一种类型,所以可以用一个函数类型的变量进行接收. func anonyTest1(){ fmt.Println("anonyTest1&

随机推荐