Q1. size of a object of empty class?

class Empty {
};

int mian() {
    Empty ept;
    std::cout << sizof(ept) << '\n';
}

A: 1. 为了让空类的对象在内存中有相应的地址对应,编译器会给每个空类的对象分配一个char大小的空间。不然的话,如何区分空类的不同对象呢?

Q1.1 size of a object of a class inherited from empty class?

class A : public Empty {
    int i
};

int mian() {
    A a;
    std::cout << sizof(a) << '\n';
}

A: 4. 这就是empty base optimization,如果把空白对象最为成员,会占一个字节,最为基类就不占空间。STL中有很多这样继承空白基类的技巧,比如:

// 5 kinds of iterator
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};

那么空类继承空类又有多大呢? 还是区分空白对象的道理:1 字节.


Q2. size of a object of empty class with functions ?

class Empty {
    public:
    void foo() {
        //...
    }
    void bar() {
        //...
    }
};

int mian() {
    Empty ept;
    std::cout << sizof(ept) << '\n';
}

A: 1 字节。 因为… 难道对象所在的内存还包含函数吗?是吗,这样的话每个对象都包含一份函数的代码?这不是很蠢?

是的,对象的方法与变量是不在一起的,对象(非全局)在栈里,对象的方法,一个类的所有对象的方法都是一样的,根本没必要放在栈上,实际上对象的方法都保存在内存的代码区,且只有一份。

那对象和对象的方法不在一起,当对象调用方法的时候怎么找得到相应的方法呢?

实际上,不是对象调用方法,是方法把对象的指针作为参数调用。

比如:

int main() {
    Empty e;
    e.foo();
}

对于类的方法foo,在编译预处理的时候,把它变成类似这样的符号:VOID_EMPTY_FOO_1,然后经过编译和链接,程序可以通过符号表找到相应的函数在代码段的地址,从而调用。在调用对象函数的时候,实际上除了函数声明的参数外,还会在栈上多push一个参数,也就是this指针啦。关于this指针,实际上就相当于是类的一个常量指针className* const this,它是一个右值,指向对象内存所在。通过this指针,函数就可以愉快地使用对象的所有成员变量了。

当然上述的讨论是针对non-static的成员方法。对于static方法,它不会使用non-static成员变量,当然不会push this指针作为参数了。