什么是内存对齐
为什么要结构体内存对齐?
可是为什么系统要对于结构体数据进行内存对齐呢,很明显所占用的空间大小要更多。
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
更简单的说明下:如图
首先,cpu的访问粒度为4,也就是一次性可以读取内存中的四个字节内容;当我们不采用内存对齐策略,如
果需要访问A中的b元素,cpu需要先取出0 ~ 3四个字节的内容,发现没有读取完,还需要再次读取,一共需
要进行两次访问内存的操作;而有了内存对齐,参考左图,可一次性取出4~7四个字节的元素也即是b,这样
就只需要进行一次访问内存的操作。所以操作系统这样做的原因也就是所谓的拿空间换时间,提高效率。
建议:虽然操作系统会浪费空间来完成内存对齐,但是我们有了上面的知识可以通过按照数据类型来调整结构体内部的数据的先后顺序来尽量减少内存的消耗;例如我们将下面结构体A中的顺序调整为B,sizeof(A)的结果为12,而sizeof(B)的结果就是8:
struct A
{
char a;
int b;
char c;
};
struct B
{
char a;
char c;
int b;
};
能否设置任意的对齐参数?
对齐参数不能任意设置,只能是内置类型已有的字节数,如:char(1)、short(2),int(4),double(8)…不能是3,5…任意数。
如何知道结构体某个成员相对于结构体起始位置的偏移量?
使用offsetof宏来判断结构体中成员的偏移地址。使用offsetof宏需要包含stddef.h头文件,该宏定义如下:
#define offsetof(type,menber) (size_t)&(((type*)0)->member)
巧妙之处在于将地址0强制转换为type类型的指针,从而定位到member在结构体中偏移位置,编译器认为0是一个有效的地址,从而认为0是type指针的起始地址。
//测试大小端的代码
int a = 0x12345678
char* b = (char*)&a;
cout<<*b<<endl;
class Singleton{
public:
static Singleton* GetIntence()
{
if (nullptr == _Intence)
{
//因为用互斥锁是非常繁重的 所以采用双检查
_mtx.lock();
if (nullptr == _Intence)
{
//判断空指针了才给申请新空间不然直接给以前申请的_Intence
_Intence = new Singleton();
}
_mtx.unlock();
}
return _Intence;
}
class CGarbo //因为上面的整个类之后一个静态的成员变量指针,也就是没有实例化任何一个对象,所以要释放静态的成员变量就必须依靠这个类。
{
//释放_Intence
public:
~CGarbo()
{
if (Singleton::_Intence != nullptr){
delete Singleton::_Intence;
_Intence = nullptr;
}
}
};
static CGarbo Garbo;//创建静态变量是为了最后调用它的析构函数
private:
Singleton(){}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&)const = delete;
static Singleton *_Intence;
static mutex _mtx;
};
Singleton* Singleton::_Intence = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::_mtx;