您的位置 首页 > 数码极客

int类型占用几个字节,int类型占用几个字节c#

C/C++语言特别适合用于开发压榨机器性能的程序,在资源有限的情况下,C/C++程序员不能放过机器的每一点计算力以及每一字节内存。当然了,要做到这一点,编程语言本身要提供精准的控制能力,C/C++语言作为一种强类型的编程语言,“精准控制”自然不在话下。这样看来,将机器性能发挥到极限的责任最后还是落在程序员身上了,要做到这一点,我们至少需要对各种数据类型使用的内存情况了然于胸。

需要对各种数据类型使用的内存情况了然于胸

类的内存模型

一般来说,我们在刚开始接触C/C++语言时,就会知道不同的数据类型占用内存空间通常不同的概念,比如 char 类型占用 1 个字节内存空间,int 类型常常占用 4 字节内存空间,double 类型常常占用 8 字节内存空间,有经验的程序员还会明白指针以及结构体占用内存空间的情况,等等。

请注意“常常”一词,C语言标准暂时还没有明确定义 int 等数据类型占用内存空间的情况。

事实上,我之前也在讨论C语言特性的时候专门写文章分析过数据类型与内存使用之间的关系,虽说也涉及到稍稍复杂一些的内存对齐概念,但是对于C++语言中的类,倒是完全没有提及,而类是C++语言中非常重要的概念,要是弄不清楚它的内存分布,“精准控制”就完全是吹牛了。

查阅了不少资料,没有找到直接讨论C++语言中类的内存分布的,我倒也不想再花时间在搜索引擎上,倒不如通过几条线索自己分析,直观的理解C++语言中类的内存分布了。

空类

请看下面这段C++语言代码:

class A { }; cout << sizeof(A) << endl; // 输出 1

看来即使是空类,编译器在处理它时,也会隐含的添加 1 个字节。

类的成员变量

现在为类 A 增加几个成员变量,并且分别将此时类 A 的 size,实例化对象的地址,以及成员变量的地址打印出来:

class A { public: int pub_i1; int pub_i2; }; A a; cout << "sizeof A: " << sizeof(A) << endl; cout << "a addr: " << &a << endl; cout << "A::pub_i1 addr: " << &a.pub_i1 << endl; cout << "A::pub_i2 addr: " << &a.pub_i2 << endl;

类的成员变量

这段C++语言代码的输出如下,请看:

sizeof A: 8 a addr: 0x7fff98567040 A::pub_i1 addr: 0x7fff98567040 A::pub_i2 addr: 0x7fff98567044

可以看出,此时类 A 的的 size 恰好等于两个成员变量占用的内存之和(我的机器上sizeof(int)等于 4),并且对象 a 的地址和第一个成员变量 pub_i1 的地址相等,第二个成员变量 pub_i2 的地址则紧跟在 pub_i1 之后,由此可以推测类 A 的内存分布如下图:

类 A 的内存分布

此时的类 A 倒有些类似于C语言中的结构体:

struct A { int pub_i1; int pub_i2; };

的确如此,事实上,类 A 中成员变量的在内存中的存储方式也会涉及到内存对齐,这一点也和C语言中的结构体类似,不过这不是本文的重点,感到陌生的读者可以再回头看看我之前的文章。

类的成员函数

为类 A 添加两个成员函数,并且将其地址打印出来:

class A { public: int pub_i1; int pub_i2; void pub_foo1() {} void pub_foo2() {} }; A a; cout << "sizeof A: " << sizeof(A) << endl; cout << "a addr: " << &a << endl; cout << "A::pub_i1 addr: " << &a.pub_i1 << endl; cout << "A::pub_i2 addr: " << &a.pub_i2 << endl; printf("A::pub_foo1() addr: %p\n", (void *)&A::pub_foo1); printf("A::pub_foo2() addr: %p\n", (void *)&A::pub_foo2);

添加两个成员函数

这段C++语言代码的输出如下:

sizeof A: 8 a addr: 0x7ffe2dbc3120 A::pub_i1 addr: 0x7ffe2dbc3128 A::pub_i2 addr: 0x7ffe2dbc312c A::pub_foo1() addr: 0x400b28 A::pub_foo2() addr: 0x400bc2

从输出可以看出,成员函数的加入并未增加类 A 的 size,而且两个成员函数 pub_foo1() 和 pub_foo2() 在地址上离类 A 的成员变量非常远,有理由推测此时类 A 在内存中的分布如下,请看:

类A的成员函数独立分布

即,类 A 的(非虚)成员函数其实在内存中是独立分布的,并且彼此之间不毗邻,它们都远离类 A 对象的地址,并不占据类 A 的 size。

类的私有成员

前面讨论的都是类 A 的公有成员,现在为其增加几个私有成员,并通过公有函数 pub_foo1() 将它们的地址打印出来,相关的C++语言代码如下,请看:

class A { ... private: int prv_i1; int prv_i2; void pub_foo1() { cout << "A::prv_i1 addr: " << &prv_i1 << endl; cout << "A::prv_i2 addr: " << &prv_i2 << endl; printf("A::prv_foo1() addr: %p\n", (void *)&A::prv_foo1); printf("A::prv_foo2() addr: %p\n", (void *)&A::prv_foo2); } void prv_foo2() {} }; ... a.pub_foo1();

类的私有成员

修改后的C++语言编译执行后输出如下,请看:

sizeof A: 16 a addr: 0x7ffdbbfe6980 A::pub_i1 addr: 0x7ffdbbfe6980 A::pub_i2 addr: 0x7ffdbbfe6984 A::pub_foo1() addr: 0x400ace A::pub_foo2() addr: 0x400bb0 A::prv_i1 addr: 0x7ffdbbfe6988 A::prv_i2 addr: 0x7ffdbbfe698c A::prv_foo1() addr: 0x400bba A::prv_foo2() addr: 0x400bc4

此时 sizeof(A) 变为 16 了,这恰好等于 4 个 int 型成员变量占用的内存之和,并且仔细观察还能发现这 4 个 int 型成员在内存中是连续分布的,并且顺序是它们在类中被定义的顺序(这一点读者可自行验证)。private 成员并无特别之处,私有函数也是独立于类 A 对象 a 独立存储的,因此推测此时类 A 的内存模型如下,请看:

private 成员并无特别之处

虚函数

我们已经知道C++语言中类的常规非虚成员函数并不占用类的 size 了,那么作为目前唯一已知动态绑定的虚函数是否也如此呢?我们在类 A 中添加两个虚函数:

class A { public: ... void pub_foo2() {} virtual void pub_vfoo1() {} virtual void pub_vfoo2() {} private: ... }; ... printf("A::pub_vfoo1() addr: %p\n", (void *)&A::pub_vfoo1); printf("A::pub_vfoo2() addr: %p\n", (void *)&A::pub_vfoo2); a.pub_foo1();

虚函数

编译并执行这段C++语言代码,可以得到如下输出,请看:

sizeof A: 24 a addr: 0x7fffb26a22a0 A::pub_i1 addr: 0x7fffb26a22a8 A::pub_i2 addr: 0x7fffb26a22ac A::pub_foo1() addr: 0x400b28 A::pub_foo2() addr: 0x400bc2 A::pub_vfoo1() addr: 0x400bcc A::pub_vfoo2() addr: 0x400bd6 A::prv_i1 addr: 0x7fffb26a22b0 A::prv_i2 addr: 0x7fffb26a22b4 A::prv_foo1() addr: 0x400be0 A::prv_foo2() addr: 0x400bea

此时类 A 的 size 为 24,增加了 8 个字节,看来虚函数的确很特别(常规函数并不增加类的 size)。还不止于此,在我的机器上,一个函数指针占用的内存空间为 8 字节,这里我们添加了 2 个虚函数,却只增加 1 个函数指针的大小,为什么呢?

还记再前面一节中我们曾提到C++语言编译器会为含有虚函数的类添加虚表存放虚函数指针吗?这里多出一个指针正是虚表指针__vptr:只需要一个指针就能够找到虚表,进而在虚表中找到所有的虚函数。请注意,含有虚函数的类 A 的对象 a 的地址已经不等于第一个成员变量的地址,而是多出了 8 个字节的内存偏移,这个偏移量恰好能够存放一个指针,因此我们推测含有虚函数的类 A 在内存中的分布如下图,请看:

含有虚函数的类 A 在内存中的分布

这种推测对不对呢?我们可以再做实验确认下,编写下面的C++语言代码:

... a.pub_foo1(); void **__vptr = (void **)&a; void **virtual_table = (void **)(*__vptr); printf("virtual table[0]: %p\n", virtual_table[0]); printf("virtual table[1]: %p\n", virtual_table[1]);

对上述C++语言代码稍作解释:我们已经分析虚表指针__vptr恰好等于类 A 对象 a 的地址,因此可以根据&a获得实际的虚表指针地址。因为__vptr指向的正是虚表(virtual_table),所以将得到的地址中的值取出就得到了 virtual_table 的地址,依次将 virtual_table 中的前 2 个(类A只有 2 个虚函数)元素的值打印出来,应该分别等于类 A 的两个虚函数地址,对不对呢?实际编译并执行这段C++语言代码,得到的输出如下,请看:

sizeof A: 24 ... A::pub_vfoo1() addr: 0x400c16 A::pub_vfoo2() addr: 0x400c20 ... virtual table[0]: 0x400c16 virtual table[1]: 0x400c20

我省去了一些信息,这样便可以清晰的看出输出其实与我们的推测一致。

小结

本文主要通过简单的C++语言代码示例分析和讨论了类在内存中的布局,最后我们还分析了所谓的“虚表”以及“虚表指针”在内存中的分布,当然了,本文并无实际的理论支撑,一切都是从实践推导出来的,但是这样多多少少可以提供一些参考,事实上,本文更多的是介绍一种分析方法,下一节将使用同样的方法分析类在继承的过程中的内存变化,敬请关注。

敬请关注

欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。

未经许可,禁止转载。

代码看着不方便的话,或者希望获得完整代码的话,可以点击文章末尾的“了解更多”。

关于作者: admin

无忧经验小编鲁达,内容侵删请Email至wohenlihai#qq.com(#改为@)

热门推荐