c/c++补完计划(四):字节对齐和虚继承

前言

猪场最爱考的内容, 亲测.

结构体大小

先来看个基础的:

#include <iostream>

#pragma pack (8)

using namespace std;
struct A {
    char a;
    int b;
    double c;
};

int main() {
    // 1: 13
    // 2: 14
    // 4: 16
    // 8: 16
    cout << sizeof(A) << endl;
}

按理说, char 1个byte, int 4个byte, double 8个byte. 应该是13个字节, 为什么会有别的答案. 甚至没有#pragma pack (v)的情况下都是16. 原因就在于字节对齐. 关于字节对齐为啥存在, 简单来说, 就是数据都是一块一块读的, 不是一个一个.
那么如何得出呢, 其实很简单, 1的情况下, 是多少就是多少. 2的话, 补成第一个大于13的2的倍数, 也就是14, 4的话, 第一个大于13的4的倍数, 也就是16, 以此类推. 而64位默认是8字节对齐.
然后你会说, 就这?

类大小

那么下面这个呢?

class B {
public:
    B(char a, int b, double c) : a(a), b(b), c(c) {}

    static int x;

    virtual void hello() {};
private:
    char a;
    int b;
    double c;
};

你可能会说还是16, 因为函数和静态变量不算在大小内. 你答对了一半, 函数是不算, 但是virtual关键字导致了虚指针的产生, 而我的mac是64位, 指针占8个字节, 所以答案是24.

那么继承一下呢?

class C : public B {
public:
    C(char a, int b, double c, int d) : B(a, b, c), d(d) {}
    
     virtual void hello() {}
private:
    int d;
};

你会说28, 不过注意, 64位默认8字节对齐, 所以是32哦. 你会说, 不对, 这里有virtual, 多一个虚指针. 不对, 这里只有一个虚指针, 继承来的, 指向自己的虚表. 所以如果面试官问你, 为什么基类指针可以动态调用子类函数, 你就可以从虚指针来作答.

虚继承

如果是菱形继承怎么办?

class B {
public:
    B(char a, int b, double c) : a(a), b(b), c(c) {}

    static int x;

    virtual void hello() {};
private:
    char a;
    int b;
    double c;
};

class C : public B {
public:
    C(char a, int b, double c, int d) : B(a, b, c), d(d) {}

    void hello() override {}

private:
    int d;
};

class D : public B {
public:
    D(char a, int b, double c, int d) : B(a, b, c), d(d) {}

    void hello() override {}

private:
    int d;
};

class E : public C, D {
public:
    E(char a, int b, double c, int d1, int d2, int d) : C(a, b, c, d1), D(a, b, c, d2), d(d) {}

    void hello() override {}

private:
    int d;
};

结果64怎么算, 首先B 24, C 28, D 28, E 28 + 28 + 4 = 60, 然后8字节对齐, 64. 如果4字节对齐就是60. 注意, 这里C, D都有虚指针, 被E继承.
如果变化下, 改成虚继承. 先来看输出:

class C : virtual public B {
public:
    C(char a, int b, double c, int d) : B(a, b, c), d(d) {}

    void hello() override {}

private:
    int d;
};

class D : virtual public B {
public:
    D(char a, int b, double c, int d) : B(a, b, c), d(d) {}

    void hello() override {}

private:
    int d;
};

image

是不是懵了, C, D变大了, E变小. 先来看E, 它继承了C, D的独有变量,但是没有继承他们从B得到的, 而是直接从B获取一份内容, 这样就是3个虚指针, B的变量, C和D的变量, 自己的变量, 也就是24+13+4+4+4=49, 8字节对齐, 等于56. 那么40怎么来的? 其实2个虚指针+B的变量+C的变量, 16+13+4=33, 8字节对齐, 40.

C++中的字节对齐(内存对齐)是指在分配内存时,将变量或结构体的起始地址对齐到特定的字节边界。这样做有助于提高内存访问的效率和性能。字节对齐的规则可以通过编译器选项或特定的关键字进行控制。 以下是关于C++字节对齐的一些重要概念和规则: 1. 默认对齐: - 编译器会使用默认的对齐规则来分配内存。通常,默认对齐值是被编译器设置的,一般为结构体成员中最大的对齐值。 2. 对齐值: - 对齐值是指要求变量或结构体的起始地址必须是该值的倍数。常见的对齐值有1、2、4、8等。 3. 对齐修饰符: - C++11引入了对齐修饰符 `alignas`,允许开发者显式地指定变量或结构体的对齐值。 4. 结构体字节对齐: - 结构体的字节对齐规则是,结构体的起始地址必须是其成员中最大对齐值的倍数。 - 编译器会在结构体成员之间插入填充字节,以保证对齐要求。 5. 类对象字节对齐: - 类对象的字节对齐规则与结构体类似,但还受到继承关系的影响。 - 派生类的起始地址必须满足其成员的对齐要求,并且满足其基类中最大对齐值的倍数。 为了控制字节对齐,可以使用编译器提供的特定选项(如`#pragma pack`)或关键字(如`alignas`)。具体的字节对齐规则和选项可能因编译器和平台而异,因此在编写代码时最好参考特定编译器的文档。正确的字节对齐可以提高内存访问性能,并确保与其他代码或外部系统的兼容性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值