C++ sizeof、 空类、 空数组学习笔记

前言:

C++是一门比较复杂的语言, 里面有很多概念都很容易搞混或者疏忽, 比如上一篇的指针问题, 这一篇里, 我主要想总结一下sizeof的用法, 以及由sizeof引发出来的一些比较平常会很模糊的概念问题, 涉及到了空类, 空数组的概念和用法, 以及C++为什么要设计这些空类空数组的存在, 所谓”存在即是理由”, 那么我们就在这里好好解析一下sizeof, 空类, 空数组这些东西里面的各中缘由吧.

sizeof 用法:

  1. sizeof严格来讲是一个一元操作符, 而不是一个函数, 也有说sizeof是一个宏定义, 说法不一, 这里能确定的就是sizeof肯定不是一个函数, 而且sizeof这个表达式的值在编译的时候就确定了

  2. sizeof的用法是sizeof(type name), sizeof(expr), 如果是expr则括号可以省略, 如果是type name则不能省略括(后面的代码中会出现编译错误如果省略了type name的括号的话), 所以最好的办法就是总是加上括号, 这或许也是sizeof总是容易误被当成函数的原因吧。

3.  sizeof (expr) 不会计算expr的表达式的结果, 而只是返回expr结果的类型的大小, 比如sizeof(++i), i 还是原来的那个数值, 要小心, 这同时也说明sizeof不是函数.

sizeof查看空类, 空数组的实验结果和分析:

既然我们现在知道了sizeof怎么用, 以及sizeof到底是个什么东西, 那么我们就来看看实际代码中使用sizeof会有哪些有趣的结果, 由于一般的基本类型或者简单的struct结构体我们都比较了解了, 这里就不做实验了. 平时我们可能不那么熟悉或者很少用到的是空类以及空数组, 这些东西往往是我们比较模糊的类型, 那好, 我们这里就主要来看看空类, 以及空数组这些个类型在内存中到底占了多少字节, 以及为什么. 如果对空类或者空数组的概念不是特别清楚的话, 可以简单查阅一下C++的任何一本书. 下面是测试代码以及结果(代码注释中):

#include <cstdio>

class EmptyNono {
};

class EmptyConstDestruct {
    public:
        EmptyConstDestruct() {}
        ~EmptyConstDestruct() {}
};

class EmptyVirture{
    public:
        EmptyVirture() {}
        virtual ~EmptyVirture() {}
};

class EmptyArray {
    int a[0];
};

int emptyArr[0];

//
// size of EmptyNono() = 1
// size of EmptyConstDestruct() = 1
// size of EmptyVirture() = 8
// size of EmptyArray() = 0
// size of emptyArr = 0
int main() {
    // printf("size of EmptyNono() = %d\n", sizeof EmptyNono);  // complile error
    printf("size of EmptyNono() = %d\n", sizeof(EmptyNono));
    printf("size of EmptyConstDestruct() = %d\n", sizeof(EmptyConstDestruct));
    printf("size of EmptyVirture() = %d\n", sizeof(EmptyVirture));
    printf("size of EmptyArray() = %d\n", sizeof(EmptyArray));
    printf("size of emptyArr = %d\n", sizeof(emptyArr));
    return 0;
}

这里总共测试了六种不同的类型结果, 我们挨个分析:
(1) sizeof EmptyNono是编译错误(compile error), 上文说了, sizeof后面跟类型的话是一定要加括号的, 当成表达式, 不加括号的话, 编译器会把EmptyNono解释成一个类型申明中的类型标号, 那么比编译器就会confuse了, 因为EmptyNono后面并没有跟上实例变量名. 这就是为什么会编译错误.

(2) sizeof EmptyNono() = 1, EmptyNono看定义我们知道这个是一个空类(类定义里面什么都没有), 但是为什么实际输出的确有一个字节呢, 这里面的本质原因是由于C++需要能够区分同一个类型的不同实例, 即使这个类型是自定义类型的空类, 如果什么都没有, 我们也不给空类的实例分配任何内存, 那我们根本无法区分空类的不同实例, 比如我拿EmptyNono这个空类申明了三个不同的实例a, b, c(空类也是我们定义的类型, 那么必然我使用空类去申明空类型的变量是完全合法的), C++需要这三个实例内存里面有点东西他才能区分这三个是不同的, 一般的做法是在空类里面自动插入一个字符型, 也就是一个字节, 从而每个空类的实例都占一个字节. 当然具体的做法应该是取决于具体的编译器的.

(3) sizeof EmptyConstDestruct() = 1, 这个和之前的第二个结果相同, 但是类定义里面多了构造函数和析构函数, 但是构造函数和析构函数的增加只和类型有关, 所有实例都会采用的是同一份析构函数或者构造函数的代码, 实例本身没有必要增加额外的内存空间去储存函数地址之类的信息. 所以结果还是1

(4)  sizeof EmptyVirture() = 8, 这个嘛, 情况就复杂一点了, 因为析构函数变成了虚函数, 那么我们知道一旦类里面有虚函数的话, 就会生成一个虚函数表(vtable), 储存所有的虚函数的地址, 实现运行时的多态. 这个虚函数表存的函数地址是消耗内存的, 而这里我们只有一个虚函数, 那么就需要四个字节(32位系统)或者8个字节(64位系统)去存这个函数指针. 我的实验机器是64位的, 所以输出8. 另外因为有了虚函数表, 我们已经有了区分空类的不同实例的依据了, 就没有必要为空类插入一个字符型. 这也就是为什么结果不是1+8=9而是8的原因.

(5) sizeof EmptyArray() = 0, 这里我们看空类的定义里面多了一个空数组(int a[0]), C/C++里面对空数组是这么解释的, 空数组名字(这里的a)是一个指针, 这个指针指向前一个对象相同的内存地址. 所以这里的空数组a是有意义的但是不消耗内存, 由于空类里面有了空数组, 那么这个空类的实例可以根据空类里面的空数组进行区分, 那也就不必要再自动给空类添加一个字符型用于区分了. 所以这里的结果是0.

(6) sizeof emptyArr = 0, 基本上第五点已经分析好了, 空数组是有意义的但是不占内存, 空数组名字指向前一个对象的相同地址但是不占内存, 这里输出0. 这里再补充一下为什么C/C++要有这种空类, 空数组的概念, 空数组的概念一般用于定于未指定长度的内存空间, 在运行时决定. 有了空数组, 可以很方便的实现变长的内存块分配. 空类的用处多用于泛型编程, 用于在编译时决定同名函数的多态选择(不同名字的空类是不同的类型, 所以可以利用这一点完成编译时的同名函数不同参数的多态选择).

总结:

这一篇主要也是在自己学习和搞明白C++里面一些模糊的概念的时候找了很多资料, 想要总结一下. 这里主要总结了sizeof的用法, 空类, 空数组的概念和用法. 杂七杂八的资料看了很多, 主要内容有参考这一篇”C/C++中,空数组、空类、类中空数组的解析及其作用“, 大家也可以参考. 全文内容均属于自己理解了以后, 凭着自己的理解的想法原创行文, 如有不当之处, 还请不吝指正.

(全文完,转载时请注明作者和出处)


(转载本站文章请注明作者和出处 烟客旅人 sigmainfy — http://www.sigmainfy.com,请勿用于任何商业用途)

Written on September 22, 2013