1、内存对齐linux
(一)、为何会有内存对齐?c++
一、为了提升程序的性能,数据结构(尤为是栈)应该尽量的在天然边界上对齐。缘由是为了访问未对齐的内存,处理器须要进行两次访问,而访问对齐的内存,只须要一次就够了。这种方式称做“以空间换时间”在不少对时间复杂度有要求问题中,会采用这种方法。数组
二、内存对齐可以增长程序的可移植性,由于不是全部的平台都能随意的访问内存,有些平台只能在特定的地址到处读取内存。数据结构
通常状况下内存对齐是编译器的事情,咱们不须要考虑,但有些问题仍是须要考虑的,毕竟c/c++是直接操做内存的语言,须要了解程序在内存中的分布和运行原理。ide
(二)、内存对齐:那么如何对齐呢?性能
对齐原则:数据存放的起始位置是自身大小的整数倍处。指针
例:内存从0地址处开始。调试
char1个字节,因此对齐后它能够存放到地址是1的倍数处。内存
short2个字节,因此对齐后它能够存放到地址是2的倍数处。编译器
int是4个字节,因此对齐后它能够存放到地址是4的倍数处。
double是8个字节,因此对齐后它能够存放到地址是8的倍数处。
如今相信你已经明白了内存对齐的原则,接下来咱们看看结构体内存对齐。
(三)、在了解结构体内存对齐以前先来了解几个概念:
一、默认对齐数:在vs下内存对齐数默认是8,linux是4.能够经过#program pack ()来修改默认对齐数。
二、偏移:相对于起始位置的的位置。例如起始位置是2,那么2就是0偏移处,3就是1偏移处。
三、对齐数:变量自身的大小和默认对齐数之中的最小值。假设默认对齐数是8,int类型的对齐数就是4.由于int大小是4,小于8
2、结构体内存对齐原则:
一、结构或联合的数据成员,第一个成员放到0偏移的地方,之后每一个数据成员都放到自身对齐数的整数倍偏移处。
二、结构体的大小必须是最大对齐数的整数倍。
例1:
struct stu
{
char c; //对齐数是1
short b; //对齐数是2
double d; //对齐数是8
int i; //对齐数是4
};
例2:
struct stu
{
char c; //对齐数是1
short b; //对齐数是2
struct A
{
double d; //对齐数是8
};
int i; //对齐数是4
}s;
嵌套结构体的大小,其分析方法仍是同样,最大对齐数是8,sizeof(s)=24。
3、自定义类型
(一)、结构体声明
一、没有标签,不完整的声明。同时还定义一个变量。
struct
{
charc;
shortb;
inti;
}t1;
二、有标签的声明,但没定义变量的声明。
struct A
{
charc;
shortb;
inti;
};
//定义一个变量struct A *s1;
//注意,在同一个程序中,同时声明一、2两个结构体,则一、2两个结构体会被认为是不一样类型的。因此 s1=&t1是错误的。
三、有标签的声明,同时还定义一个变量。
struct A
{
charc;
shortb;
inti;
}t3;
四、声明的同时对结构体重命名为A.
typedef struct A
{
charc;
shortb;
inti;
}A;
五、先有鸡仍是先有蛋
struct B //不管哪一个放到前面都不对
{
structAa;
}s;
structA
{
structBb;
};
若是两个结构体相互嵌套,则在声明的时候须要对其中一个结构体进行不完整的声明。
structA;
struct B
{
structAa;
}s;
structA
{
structBb;
};
(二)、结构体的初始化:
例如:
typedef struct A
{
charc;
shortb;
inti;
}A;
As1 = {'c', 2 , 4 };
(三)、结构体的自引用:(结构体的自引用一般会用在链表这种线性结构中用到)
一、错误的自引用方式,很容易理解的,结构体里面又有结构体,这样一直循环下去。(从前有座庙,庙里有个老和尚,老和尚给小和尚讲故事..........^v^)
typedef struct A
{
intdata;
structAn; //死循环
}A;
二、错误的只引用,由于结构体被从新命名为A是在引用以后。
typedef struct //在结构体自引用的时候标签不能省略。
{
intdata;
An; //必须使用完整的结构体名称
}A;
三、正确的方式
typedef struct A
{
intdata;
structA *n; //用完整的结构体名称,声明一个结构体指针,
}A;
(四)、结构体作参数传递的效率:
当结构体很大时,结构体在做为参数传递时,咱们传递它的地址,这样可以提升效率,若是你不想改变结构体内容,则在形参处加上const就行。
(五)、柔性数组:
在结构体中最后一个成员容许是未知大小的数组,这个数组成为柔性数组(柔性数组以前至少有一个成员变量)
typedef struct A
{
inti;
char a[];
}A;
含有柔性数组的结构体大:这样的结构体,它的大小不包括柔性数组,因此sizeof(A)=4;空结构体的大小是1;
(六)、位段(位域):
一、概念:在一个结构体中以位为单位来指定成员所占内存的实际大小,这种以位为单位的成员咱们称为位段,位段是一种特殊的结构体,位段的声明和任何普通的结构体成员声明相似,以下:
Struct 位段结构体名
{
Unsigned 位段名:位段长度;
Unsigned 位段名:位段长度;
………………..
Unsigned 位段名:位段长度;
}位段结构体变量名;
但有两个例外,首先位段成员必须声明成int ,unsigned int, signed int,。其次,在成员的后面是一个冒号和一个整数,这个整数指定该位段所占用位的个数。(实际验证后发现char类型也能够,可是注意,位段中不能将int 和char 混合使用)。
二、 位段使用时须要注意是:
一、位段结构体中的成员不能使用数组和指针,但结构体变量可使数组或者指针。
二、由于数组和指针都是以字节为单位的,同理也不能用&获取位段的地址。
三、位段不支持移植。
例1:声明一个位段,咱们先来分析一下他在计算机里面是如何存储的(一个无符号的int是4字节)。
struct tagAAA
{
unsigned int a : 1;
unsigned int b : 2;
unsigned int c : 6;
unsigned int d : 4;
unsigned int e;
}AAA_S;
由此咱们能够明白位段的优势,原本定义了5个成员,须要5个存储单位,可是使用位段后只须要4个存储空间就足够了。
三、优势:
但它的成员是一个或多个位的字段,这些不一样长度的字段其实是存储于一个或多个×××变量中,他的优势是可以以较少的内存单元存储数据。位段能够用×××形式输出。
例2:
struct tagAAA
{
unsigned int a : 1;
unsigned int : 2; //没有声明变量,可是却指定位段大小,称为占位。
unsigned int c : 6;
unsigned int d : 4;
unsigned int e; //没有指定位段大小,默认为自身类型的大小
}AAA_S;
(七)、联合
一、联合的声明:
typedefunionA
{
inti;
charc;
}A;
二、联合的特色:
联合成员之间共用同一块空间。联合的大小等于成员中所占内存最大变量大小。能够用来测大小端。
(八)、枚举:
一、声明:
typedefenumA
{
zero,
one,
two
}A;
若是没有对枚举成员进行初始化时,则默认枚举成员从0开始依次递增
注意:
一、在同一个程序中,不能不能声明同名的枚举类型
二、在同一个程序中,不一样的枚举类型的枚举成员不能同名。
三、任何枚举的大小都是4
二、枚举与#define 标识符之间区别:
一、#define 标识符在预编译期间进行简单替换。枚举类型在编译的时候肯定其值。
二、枚举常量能够调试,#define 标识符不能够。
三、枚举一次能够定义大量的枚举量。