C++中的内存对齐

最近在复习c++,看到了sizeof部分,对使用它来判断变量的长度有一点蒙,在网上四处找资料,在这里做一个总结

环境

  • MacOS 64位
  • g++ 4.2.1
  • 不加任何预编译指令(比如 #pragma pack)

参考内容

这个我就写在前面了,因为这些文字或答案写的真的很好,我后面做的只是把它们整合在一起


普通的变量

1
2
3
4
5
char c;
short s;
int i;
double d;
std::cout << sizeof(c) << " " << sizeof(s) << " " << sizeof(i) << " " << sizeof(d);

输出如下

1 2 4 8

非常正常,要多大空间给多大空间

分析


结构体

关于结构体

关于结构体变量分配有几个准则

  • 结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充字节
  • 结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节,否则需要补充字节直到满足要求

简单的例子

1
2
3
4
5
6
struct S1 {
char c;
short s;
int i;
double d;
};

分析如下

分析


相对复杂的例子

那么,如果把 int 放到第二个呢?为了增加复杂性,再在最后加一个 char 类型的变量,如下

1
2
3
4
5
6
7
struct S1 {
char c;
int i;
short s;
double d;
char cc;
};

分析如下

分析


类的大小

所有指针的大小为8

1
std::cout << sizeof(char *) << " " << sizeof(int *) << " " << sizeof(double *);

以上输出均为8

类中包含方法以及静态成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Base {
public:
Base() {};

void func() {
}

void func2() {
}
static int data;
};

Base b{};
std::cout << sizeof(b) << std::endl;

输出为1;

这些方法都是类的所有实例之间共享的,那个静态成员也和实例化的对象无关;

个人觉得就像是js中把方法挂载到prototype上一样;


空类的大小

  • 构造函数,析构函数都可以看成是函数,不占空间,
  • 因为一个空类也要实例化,所谓类的实例化就是在内存中分配一块地址,每个实例在内存中都有独一无二的地址。
  • 所以编译器会给空类隐含的添加一个字节,这样空类实例化之后就有了独一无二的地址了。

最后输出为1

1
2
3
4
5
6
7
8
9
10
class Base {
public:
Base() {};
~Base() = default;
};

// ....
Base b{};
std::cout << sizeof(b) << std::endl;
// 1

类中含有虚函数

关于虚函数的具体情况就不多数了,就是它一个指针指向一个虚函数表,而一个指针占8字节,所以输出都为8

同时,不管加多少虚函数结果都是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Base {
public:
Base() {};

virtual ~Base() = default;
};

class Base2 {
public:
Base2() {};

virtual void func() {};

virtual ~Base2() = default;
};
//.....
Base b{};
Base2 b2{};
std::cout << sizeof(b) << " " << sizeof(b2) << std::endl;

类中包含属性

把之前结构体里的数据贴过来,输出的结果是一样的,为32

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Base {
public:
Base() {};
private:
char c;
int i;
short s;
double d;
char cc;
};

// ....
Base b{};
std::cout << sizeof(b) << std::endl;

类的继承

这个也只是和属性有关,子类中包含父类的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Base {
private:
char c;
int i;
short s;
};

class Derive : public Base {
public:
Derive():Base(){};
private:
double d;
char cc;
};

Base b{};
Derive d{};
// 12 32
std::cout << sizeof(b) << " " << sizeof(d) << std::endl;

虚类继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base {
public:
virtual void func() = 0;
};

class Derive : public Base {
public:
Derive() : Base() {};
void func() {}
};

int main() {
Derive d{};
std::cout << sizeof(Base) << " " << sizeof(d);
}

输出为 8 8,说明子类实现了虚类的方法,依然有一个指针指向那个虚方法列表