结构和联合--结构体内存和位段内存开辟规则
一. 结构的基本知识
聚合数据类型能够存储多个数据,C语言提供了两种类型的聚合数据类型,数组和结构。数组是相同的数据,结构是不同类型的数据聚合。结构也是一些值得集合,这些值成为它的成员,每个结构都有它的名字,他们是通过名字来访问的。
1. 结构声明
在结构声明时,必须列出它包含的所有成员,这个列表包括每个成员的类型和名字。
struct tag {member-list}variable-list;
结构体的相关语法:
strict {
int a;
char b;
float c;
} x;
这个声明创建了一个名字叫做x的结构体变量。它包含了三个元素。
struct {
int a;
char b;
float c;
} y[20], *z;
这个声明中y创建了一个结构体数组,它包含了20个结构体,z是一个指向结构体的指针变量。
警告:虽然上面的两种结构的成员的数据类型一样,但是编译器会把它们当成是完全不同的数据类型,所以下面的这条语句是非法的:z=&x;
如果想定义一个指针变量使它可以指向x应该怎么定义呢,下面提供了两种方式:
我们可以在定义的时候给这个结构体加上一个标签,比如:
首先定义一个结构体变量
struct SIMPLE {
int a;
char b;
float c;
} x;
接着定义一个指向结构体变量的指针
struct SIMPLE *p;
这个时候我们就可以使用p =&x;了。
为了方便使用我们可以给这个变量定义一个新的类型
typedefstruct SIMPLE {
int a;
char b;
float c;
} SIMPLE;
这个时候,SIMPLE就是一个新的数据类型,如果我们想定义一个结构体变量,就可以使用SIMPLE x;定义一个指向结构体的指针就可以使用SIMPLE *p;这个时候我们可以使用p =&x;
2. 结构成员
结构体中可以有很多不同的类型,如:
struct COMPLEX{
float f;
int a[20];
long *lp;
struct SIMPLE s;
struct SIMPLE sa[10];
struct SIMPLE *sp;
};
这里我们不必要担心不同的结构体中的成员名相同,因为我们的结构体是不同的。
3. 结构成员的直接访问
如果我们已经知道结构变量的名字,这里我们通过操作符(.)进行访问,比如:struct COMPLEX comp;如果我们想要访问a,则可以通过以下的方式comp.a;这里我们还可以通过下面的方式访问结构体里面的结构体的一个成员(comp.s).a;这里就是我们上面说的不同的结构体中可以定义名字相同的变量。我们甚至可以使用下面更为复杂的方式进行访问((comp.sa)[4]).c;又因为下标引用和点操作符具有相同的优先级,他们的结合性都是从左到右。
4. 结构成员的间接访问
如果我们有一个指向结构体的指针,我们想通过这个指针访问这个结构体,我们可以使用间接访问操作符。举个例子,假定一个函数的参数是个指向结构的指针,如下面的原型所示:
voidfunc(struct COMPLEX *cp );
这个函数可以使用下面这个表达式来访问这个变量所指向的结构的成员f:
(*cp).f
但是我们平时并不使用这种方式访问一个结构体的成员变量,C语言提供了一个->操作符(也称箭头操作符)使用的方式如下:
cp-> f;
5. 结构体的自引用
结构体的自引用是否合法呢,请看下面的一个例子:
structSELF_REF1 {
int a;
int c;
structSELF_REF1 b;
}
这种类型的定义是非法的,因为成员b是另一个完整的结构,其内部还将包含其他的成员b。这样就永无止境的重复下去。
再来看下面的这种定义:
structSELF_REF1 {
int a;
int c;
structSELF_REF1 *b;
}
这个声明和上面的区别在于,声明b的时候不是声明的一个结构体,而是声明了一个指向结构体的一个指针,编译器在结构体的长度确定之前就已经知道了指针的长度,所以这种自引用是合法的。(这其实就是我们在学习数据结构的时候,使用链表的一种很好的体现)
但是我们需要警惕下面的这种声明:
typedefstruct {
int a;
int c;
SELF_FER3 *b;
}SELF_FER3;
这种声明是错误的,类型名在声明的末尾才定义,所以在结构体声明的时候,它还没有被定义。
解决的方法很简单,
typedefstructSELF_FER3_TAG {
int a;
int c;
struct SELF_FER3_TAG *b;
}SELF_FER3;
6. 不完整的声明
偶尔我们需要声明相互之间存在依赖关系的结构。即是一个结构中包含了另一个结构体的一个或者多个成员。和自引用结构体一样,至少有一个结构必须在另一个结构体内部以指针的形式存在。
这样我们就必须要使用不完整的声明,它声明一个作为结构标签的标识符。然后我们可以把这个标签于成员列表联系在一起。看下面的这个例子:
structB;
struct A{
struct B*partner;
};
struct B{
struct A*partner;
}
7. 结构体的初始化
一个位于一对花括号内部、由逗号分隔的初始值列表课用于结构体的各个成员的初始化。如果初始值列表的值不够,剩余的结构体成员将使用缺省的值进行初始化。例如:
struct INIT_EX{
int a;
short b[10];
Simple c;
} x = {10,{1,2,3,4,5},{25,’x’,1.9}}
二. 结构、指针和成员
三. 结构体的存储分配
1. 结构体中存在对齐的原则,即开辟结构体空间的时候并不是按照每个成员的总大小开辟存储空间,(在C++中类实例化的对象也是按照结构体的方式分配存储空间的)看下面这样的一个简单的例子:
struct ALIGN {
char a;
int b;
char c;
};
假设我们的机器是32位的,这个时候首先开辟一个字节,用于存储a,然后看下一个成员是4个字节,为了对齐,前面空3个字节,这时前面的字节数就是int字节数的倍数,接着就是从第五个字节开始开辟4个字节用于存储b,然后是c,它的字节是1个字节,前面已经有八个字节正好是1的倍数,所以直接在b的后面开辟一个字节用于存储c。
优化:我们在定义结构体的时候可以把复杂的放在前面,把简单的放在后面,比如上面的结构体可以如下定义:
struct ALIGN {
int b;
char a;
char c;
};
如果我们用sizeof测试第一个结构的时候,它所测出来的结构包括了空白的结构,即为了对齐而跳过的内存空间,它也被包含进结构体中了。
如果我们想要确定某个成员的实际存储位置,这个时候我们可以使用offsetof宏(定义于stddef.h)
offsetof(type,member)
type是结构体的类型,member是需要测试的成员名,表达式的结构是一个size_t值(无符号整型),表示这个结构成员开始的位置距离整个结构开始的位置偏移的字节数。例如对第一个结构进行下面的使用,offsetof(structALIGN,b);返回值是4。
2. 结构体的内存对齐问题(对上面问题的详细解读)
内存对齐的含义:当结构体中每次放入一个新的成员的时候,它的前面需要补充的空白的字节数。即是:先对齐,再放入。
(1) 对齐数:自身字节数和编译器默认字节数中的较小值(vs:8 linux:4)
(2) 第一个成员放入的时候不需要对齐
(3) 接下来的成员放入的时候,需要对齐到对齐数的整数倍(补充空白字节,使前面的所有字节数的和为对齐数的整数倍)
(4) 上面对齐完之后需要对整个结构体进行对齐,即在所有的成员开辟完空间之后,还需要加入空白的空间,以使得整个结构体对齐,方式为:结构体的总大小是最大对齐数的整数倍。结构体的对齐数是该结构体中的最大对齐数。(我们可以把结构体当成是一个数据类型,这个时候结构体也是有对齐数的,但是这个对齐数不是结构体的大小,而是结构体的最大对齐数的大小)
(5) 数组拆成单个元素进行对齐
修改对齐数的大小,在源文件的一开始处输入:#pragma(4),这条语句的作用是改变系统默认的对齐数,一般往小的改。
四. 作为函数参数的结构
把结构作为参数传递给一个函数是合法的,但是这种做法往往是并不适宜的。
这里我们定义一个结构体
typedefstruct
{
…
…
}Transaction;
这个结构体可能很大,我们编写一个传递结构体的一个函数
void print(Transaction trans)
{
…
…
}
如果我们在主函数中定义了一个定义了一个结构体Transaction current_trans;
然后调用函数print(current_trans);我们知道C语言参数传址调用方式要求把参数的一份拷贝传递给函数。如果这个结构体太大,我们必须把很大一个字节复制到堆栈中,以后再丢弃,这样很影响效率。
再来看下面的函数
void print(Transaction *trans)
{
…
…
}
调用的时候使用
print(¤t_trans);这时我们传递的是一个指针,指针传递的时候要比直接把结构体传递过去小的多,这样压栈的效率就高,传递指针访问结构体也是需要付出一定的代价的,就是我们在函数中要使用间接访问来访问结构体成员。但这对我们来说影响不大。
使用指针传递的时候还有一个缺陷就是,函数内可以对主函数中的结构体进行修改,如果我们想要防止在外部函数修改结构体,我们可以在函数中使用const进行修饰。
还有一点需要主要的是,结构体在传参的时候不会退化成地址,只会发生原始的拷贝,这也是我们使用指针传参的一个原因。
综上我们建议在使用结构体传递参数的时候,可以使用地址传递的方法。
五. 位段
位段的声明和任何普通的结构体成员相同,但是有两个例外。首先位段成员必须声明为int、signed int或者是unsigned int类型。其次,在成员名的后面是一个冒号和一个整数,这个整数指定该位段所占用的位的数目。
这里建议用signed 或者unsigned进行声明,因为如果直接声明为int,它究竟是有符号的还是无符号的整型,这是由编译器而定的,可能会给我们造成不必要的麻烦。
这里通过程序来给大家说明一下位段的内存开辟规则,这里是在结构体内存开辟的规则基础之上的
#include<stdio.h>
#include<windows.h>typedef struct Stu
{char name : 2;char age : 2;char sex : 5;
}Stu;int main()
{Stu student;printf("%d\n", sizeof(student));system("pause");return 0;
}
这里的结果是多少呢,答案是2,这里位段的作用就是想充分的利用内存空间,首先开辟一个字节大小即是八个位,用于存储name,但是这里name只占用了两个位,然后age需要占用两个位,回去看,刚刚开辟的字节还有六个位,于是拿出两个位给了age,接着是sex,它需要五个位,但是刚刚开辟的字节只有四个位了,不够五个位,于是又开辟了一个字节,拿出其中的五个位存放sex,第一次开辟的一个字节剩余的四个位就浪费掉了。这里我们再次把结构体给改一下,再来看一下测试的结果是多少呢
typedef struct Stu
{char name : 1;int age : 2;char sex : 5;char address : 3;int add :1;
}Stu;
这里的测试结果是16,为什么呢,这是有原因的对吧,首先开辟一个字节给name,name占用了其中的一位,剩余七个位,然后遇到age,大家请注意age的类型是int型的,虽然只需要用到一个位,但是上面剩余的七个位不能给这个age,于是编译器开辟了四个字节,大家这里还容易犯的一个错误是,认为这四个字节直接在刚刚开辟的一个字节后面开辟,这是错误的。正确的方式是,需要对齐,前面开辟了一个字节,这里需要对齐到四,所以跳过了剩下了的三个字节,开辟了四个字节,这样之后就开辟了八个字节,然后下面的方式一样,在开辟一个字节用于存放sex和address,然后跳过三个字节,在开辟一个字节用于存放add,这样的结果就是16个字节,这里大家可以结合上文中的结构体对齐问题好好的探索一下。
提示
注重可移植性的程序应该注意,由于下面的这些实现相关的依赖性,位段在不同的系统中可能有不同的结果。
六. 联合体
联合体的所有成员引用的是内存中的相同位置,当我们想在不同的时候把不同的东西存储在同一个位置时,就可以使用联合体。联合体也需要对齐,它的对齐方式要适合所有的成员。
1. 变体记录
2. 联合的初始化
联合变量可以被初始化,但是这个初始值必须是联合第一个成员的类型,而且它必须位于一对花括号里面。例如
union{
int a;
float b;
char c;
} x = {5};
把x.a初始化为5.
我们不能把这个类量初始化为一个浮点值或者字符值。如果给出的初始值是任何其他类型,它就会转换(如果有可能的话)为一个整数并赋值给x.a。
相关文章:

antd自定义分页器_自定义分页器
classPagination(object):def __init__(self, current_page, all_count, per_page_num10, pager_count11):"""封装分页相关数据:param current_page: 当前页:param all_count: 数据库中的数据总条数:param per_page_num: 每页显示的数据条数:param pager_count:…

.net实现跨页面传值
//一般用于向php,jsp等传值,因为跨语言session等不能共用,只有通过post提交 //下面演示的是服务器端控件提交 PostBackUrl"WebForm3.aspx"//这个页面只需要修改控件属性就能把值传给下一页面 protected void Page_Load(object send…

进程的同步、互斥以及PV原语
在处理进程间的同步与互斥问题时,我们离不开信号量和PV原语,使用这两个工具的目的在于打造一段不可分割不可中断的程序。应当注意的是,信号量和PV原语是解决进程间同步与互斥问题的一种机制,但并不是唯一的机制。 信号量ÿ…

ListT中,Remove和RemoveAt区别
Remove删除的是匹配的第一项。比如你的list里面有2个相同的项。那么就删除第一个。后面的不删除,找不到元素和删除失败都返回falseRemoveAt是删除索引下的项 转载于:https://www.cnblogs.com/mcyushao/p/9526208.html

vue 如何处理两个组件异步问题_Vue动态异步组件实现思路及其问题
前言:在vue官方资料中,我们可以可以很学会如何通过vue构建“动态组件”以及“异步组件”,然而,在官方资料中,并没有涉及到真正的“动态异步”组件,经过大量的时间研究和技术分析,我们给出目前比…

[转载] 七龙珠第一部——第004话 掳人的妖怪——乌龙
转载于:https://www.cnblogs.com/6DAN_HUST/archive/2013/04/07/3003566.html

如何解决资料下载下来为index.html和PHP文件的问题?
最近很多Down友反映,在下载中心下载资料时,明明是pdf、rar、zip格式的文件,下载完后怎么就变成index.html、php格式的文件了?既浪费了下载豆,文件还不能用,心疼啊!这是因为下载系统是动态获取的…

给大家推荐8个SpringBoot精选项目
前言 2017年,曾在自己的博客中写下这样一段话:有一种力量无人能抵挡,它永不言败生来倔强。有一种理想照亮了迷茫,在那写满荣耀的地方。 如今2018年已过大半,虽然没有大理想抱负,但是却有自己的小计划。下面…

点击Notification正确回调到之前已经放置在后台的Task中的对应Activity,而不是创建它的一个新实例...
NotificationManager notificationManager (NotificationManager)getSystemService(NOTIFICATION_SERVICE);Notification notification new Notification(R.drawable.logo_icon_16,"移动营销", System.currentTimeMillis());Intent intent new Intent(Intent.ACTI…

函数返回类的对象与拷贝构造函数
C中,如果我们在一个函数中,定义了一个类的对象,然后返回这个对象,在main函数中用一个对象去接受这个返回的对象的时候,这里面参与的函数调用大家可能不熟悉,这里通过程序和注释的方式给大家讲解一下。编译的…

ai条码插件免安装_ai条码插件2款下载|Barcode Toolbox插件+Barcode条码插件下载 - 偶要下载站...
本次一次性打包两款ai条码插件和大家分享,分别是Barcode Toolbox插件和Barcode脚本插件,支持Illustrator CS5~CC2015的条形码脚本!这两个插件不是一个插件,是有区别的两个插件。Barcode Toolbox是AI的一个非常有用的生成条码的插件…

GridView的DataKeyNames属性 转载的
偶今天用到这个了,转载 "事在人为"楼主的,原文地址: http://www.cnblogs.com/andhm/archive/2010/05/07/1730024.html DataKeyNames表示主键的列名,可以通过GridViewEntity.DataKeys[RowIndex]["ColumsName"]来获取他的值,当然它是…

反射 -- 通过字符串操作对象中的成员
getattr()setattr()hasattr()delattr()class C:def __init__(self, name):self.name namedef f(self):return Pythonobj C(Pyhton) get_name getattr(obj, name) get_func getattr(obj, f) get_func() hasattr(obj, name) setattr(obj, age, 10) delattr(obj, name)转载于:…

android默认exported_android:exported 属性详解
转自http://blog.csdn.net/watermusicyes/article/details/46460347昨天在用360扫描应用漏洞时,扫描结果,出来一个Android:exported属性,其实之前根本不知道这个属性,更不知道这个属性用来干嘛的,详情见下图࿱…

Chipset
Chipset 芯片组是一组集成电路(芯片)用于管理计算机处理器、内存和外设的数据流,通常位于主板上。 Northbridge (Memory Controller Hub) 北桥用来处理高速信号,负责CPU、RAM、AGP和PCI Express之间的通信。 Southbridge (I/O Con…

正确设置php-fpm和nginx防止网站被黑
2019独角兽企业重金招聘Python工程师标准>>> 核心总结:php-fpm 子进程所使用的用户,不能是网站文件所有者。 凡是违背这个原则,则不符合最小权限原则。 根据生产环境不断反馈,发现不断有 php网站被挂木马,绝…
一个数字键盘引发的血案——移动端H5输入框、光标、数字键盘全假套件实现...
https://juejin.im/post/5a44c5eef265da432d2868f6 为啥要写假键盘? 还是输入框、光标全假的假键盘? 手机自带的不用非得写个假的,吃饱没事干吧? 装逼?炫技? 宝宝也是被逼的,宝宝也很委屈~.~ …

姿态检测 树莓派_怎样在树莓派上轻松实现深度学习目标检测?
原标题:怎样在树莓派上轻松实现深度学习目标检测?雷锋网按:本文为 AI 研习社编译的技术博客,原标题 How to easily Detect Objects with Deep Learning on Raspberry Pi,作者为 Sarthak Jain。翻译 | 小哥哥 狒狒 校对…
Linux目录读写和可执行权限
一 . 进入目录权限如果我在普通用户下创建了一个目录f1,然后使用chomd u-rwx,g-rwx,o-rwx之后,我在普通用户下想进入f1目录,权限不允许。然后我切换到超级用户下,再次尝试进入到f1目录,这个时候允许进入。然后回到普通…

【译】表变量和临时表的比较(转)
关于表变量是什么(和表变量不是什么),以及和临时表的比较让很多人非常困惑。虽然网上已经有了很多关于它们的文章,但我并没有发现一篇比较全面的。在本篇文章中,我们将探索表变量和临时表是什么(以及不是什…

grub加密。
一、介绍 安全无小事 linux系统的安全分为很多方面,什么端口啊,什么网络啊,听着都特么烦,今天谈谈最简单明显的密码安全。 二、单用户模式 单用户模式个人觉得相当有用,可以用来修复系统,修改密码…… 但是…
Linux下stat + 文件名后, Access,Modify,Change的含义
我们首先在一个目录下创建了一个文件使用命令touch file然后输入命令:stat file,这个时候会输出一系列信息大家注意红色框中的三个时间Access : 文件最近一次被访问的时间Modify: 文件内容最近一次被修改的时间Change: 文件属性最近一次被改变的时间接着…

基于设计模式的学习之旅-----访问者模式(附源码)
基于设计模式的学习之旅-----访问者模式 1、初始访问者模式 2、什么是访问者模式 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 3、模式结构图 4、模式代码事例 场景:年会,每个小组表演…

x is y python_Python 基础
2.1 程序与用户交互在python3中#input:无论用输入何种类型,都会存成字符串类型nameinput(please input your name:) #name18print(id(name),type(name),name)在python2中#raw_input与python3的input是一样的nameraw_input(please input your name:)print…

【leetcode 简单】 第八十九题 赎金信
给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成。如果可以构成,返回 true ;否则返回 false。 (题目说明:为了不暴露赎金信字迹,要从杂…
创建专属博客栏目
今天给大家get新技能了,是不是很期待捏我们一般看到的博客页面是这样的但是你是不是特别期待这样的捏其实技术上面也不是特别的 难,我们登录自己的csdn博客,然后选择“管理博客”,跳转页面之后选择“博客栏目”进入到这个页面之后…

《帝企鹅日记》观后感
第一次看到是在高中的英语周报上,那时候蛮好奇的,企鹅也写日记,呵呵,后来想了想应该是纪录片,时隔三年,发现当初的猜测果然不假。 我觉得那些企鹅很可爱,也很漂亮。最重要的是,那一条…

合并道路_资质改革已经确定!盘点被合并的资质!有这资质的要注意了
资质改革可以说是在行业里掀起了轩然大波,众多资质面临改革,有的资质被取消,有的资质被合并,有的资质继续保留。此文就带大家来盘点一下那些在近期资质定稿中被取消,被改变的资质:勘察资质勘察资质中&#…

如何为ccflow工作流引擎增加一个优先级PRI?
为什么80%的码农都做不了架构师?>>> 如何为ccflow工作流引擎增加一个优先级PRI? 对于一条流程的优先级可分为 低,中,高三个级别,用这个状态来标示这条流程的紧急程度。 以以前版本的ccflow中是以节点表…

SQL Server (MSSQLSERVER) 服务因 2148081668 服务性错误而停止。
https://zhidao.baidu.com/question/151448005.html 具体步骤:运行-> 输入:“services.msc” ->找到 “SQL Server (SQLEXPRESS)” 服务,双击 -> 在“SQL Server (SQLEXPRESS) 的属性”界面,点登录 -> 默认登录身份是…