您的位置:首页 > 其它

内存对齐与自定义类型

2016-09-06 14:21 239 查看
一、内存对齐

(一)、为什么会有内存对齐?
1、为了提高程序的性能,数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因是为了访问未对齐的内存,处理器需要进行两次访问,而访问对齐的内存,只需要一次就够了。这种方式称作“以空间换时间”在很多对时间复杂度有要求问题中,会采用这种方法。



2、内存对齐能够增加程序的可移植性,因为不是所有的平台都能随意的访问内存,有些平台只能在特定的地址处处读取内存。

一般情况下内存对齐是编译器的事情,我们不需要考虑,但有些问题还是需要考虑的,毕竟c/c++是直接操作内存的语言,需要了解程序在内存中的分布和运行原理。

(二)、内存对齐:那么如何对齐呢?
对齐原则:数据存放的起始位置是自身大小的整数倍处。
例:内存从0地址处开始。
char1个字节,所以对齐后它可以存放到地址是1的倍数处。
short2个字节,所以对齐后它可以存放到地址是2的倍数处。
int是4个字节,所以对齐后它可以存放到地址是4的倍数处。
double是8个字节,所以对齐后它可以存放到地址是8的倍数处。

现在相信你已经明白了内存对齐的原则,接下来我们看看结构体内存对齐。

(三)、在了解结构体内存对齐之前先来了解几个概念:
1、默认对齐数:在vs下内存对齐数默认是8,linux是4.可以通过#program pack ()来修改默认对齐数。
2、偏移:相对于起始位置的的位置。例如起始位置是2,那么2就是0偏移处,3就是1偏移处。



3、对齐数:变量自身的大小和默认对齐数之中的最小值。假设默认对齐数是8,int类型的对齐数就是4.因为int大小是4,小于8

二、结构体内存对齐原则:
1、结构或联合的数据成员,第一个成员放到0偏移的地方,以后每个数据成员都放到自身对齐数的整数倍偏移处。
2、结构体的大小必须是最大对齐数的整数倍。
例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。

三、自定义类型

(一)、结构体声明
1、没有标签,不完整的声明。同时还定义一个变量。
struct
{
charc;
shortb;
inti;
}t1;

2、有标签的声明,但没定义变量的声明。
struct A
{
charc;
shortb;
inti;
};
//定义一个变量struct A *s1;
//注意,在同一个程序中,同时声明1、2两个结构体,则1、2两个结构体会被认为是不同类型的。所以 s1=&t1是错误的。

3、有标签的声明,同时还定义一个变量。
struct A
{
charc;
shortb;
inti;
}t3;

4、声明的同时对结构体重命名为A.
typedef struct A
{
charc;
shortb;
inti;
}A;

5、先有鸡还是先有蛋
struct B //无论哪个放到前面都不对
{
structAa;
}s;
structA
{
structBb;
};

如果两个结构体相互嵌套,则在声明的时候需要对其中一个结构体进行不完整的声明。
structA;
struct B
{
structAa;
}s;
structA
{
structBb;
};

(二)、结构体的初始化:
例如:
typedef struct A
{
charc;
shortb;
inti;
}A;
As1 = {'c', 2 , 4 };

(三)、结构体的自引用:(结构体的自引用通常会用在链表这种线性结构中用到)

1、错误的自引用方式,很容易理解的,结构体里面又有结构体,这样一直循环下去。(从前有座庙,庙里有个老和尚,老和尚给小和尚讲故事..........^v^)
typedef struct A
{
intdata;
structAn; //死循环
}A;

2、错误的只引用,因为结构体被重新命名为A是在引用之后。
typedef struct //在结构体自引用的时候标签不能省略。
{
intdata;
An; //必须使用完整的结构体名称
}A;

3、正确的方式
typedef struct A
{
intdata;
structA *n; //用完整的结构体名称,声明一个结构体指针,
}A;

(四)、结构体做参数传递的效率:
当结构体很大时,结构体在作为参数传递时,我们传递它的地址,这样能够提高效率,如果你不想改变结构体内容,则在形参处加上const就行。

(五)、柔性数组:
在结构体中最后一个成员允许是未知大小的数组,这个数组成为柔性数组(柔性数组之前至少有一个成员变量)
typedef struct A
{
inti;
char a[];
}A;
含有柔性数组的结构体大:这样的结构体,它的大小不包括柔性数组,所以sizeof(A)=4;空结构体的大小是1;

(六)、位段(位域):

1、概念:在一个结构体中以位为单位来指定成员所占内存的实际大小,这种以位为单位的成员我们称为位段,位段是一种特殊的结构体,位段的声明和任何普通的结构体成员声明类似,如下:

Struct 位段结构体名
{
Unsigned 位段名:位段长度;
Unsigned 位段名:位段长度;
………………..
Unsigned 位段名:位段长度;

}位段结构体变量名;

但有两个例外,首先位段成员必须声明成int ,unsigned int, signed int,。其次,在成员的后面是一个冒号和一个整数,这个整数指定该位段所占用位的个数。(实际验证后发现char类型也可以,但是注意,位段中不能将int 和char 混合使用)。

2、 位段使用时需要注意是:
1、位段结构体中的成员不能使用数组和指针,但结构体变量可以使数组或者指针。
2、因为数组和指针都是以字节为单位的,同理也不能用&获取位段的地址。
3、位段不支持移植。
例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个存储空间就足够了。

3、优点:
但它的成员是一个或多个位的字段,这些不同长度的字段实际上是存储于一个或多个整形变量中,他的优点是能够以较少的内存单元存储数据。位段可以用整形形式输出。

例2:
struct tagAAA
{
unsigned int a : 1;
unsigned int : 2; //没有声明变量,但是却指定位段大小,称为占位。
unsigned int c : 6;
unsigned int d : 4;
unsigned int e; //没有指定位段大小,默认为自身类型的大小
}AAA_S;

(七)、联合

1、联合的声明:
typedefunionA
{
inti;
charc;
}A;

2、联合的特点:
联合成员之间共用同一块空间。联合的大小等于成员中所占内存最大变量大小。可以用来测大小端。

(八)、枚举:
1、声明:
typedefenumA
{
zero,
one,
two
}A;
如果没有对枚举成员进行初始化时,则默认枚举成员从0开始依次递增

注意:
1、在同一个程序中,不能不能声明同名的枚举类型
2、在同一个程序中,不同的枚举类型的枚举成员不能同名。
3、任何枚举的大小都是4

2、枚举与#define 标识符之间区别:
1、#define 标识符在预编译期间进行简单替换。枚举类型在编译的时候确定其值。
2、枚举常量可以调试,#define 标识符不可以。
3、枚举一次可以定义大量的枚举量。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息