码迷,mamicode.com
首页 > 编程语言 > 详细

C++对象之内存(无继承)

时间:2019-11-24 15:33:13      阅读:79      评论:0      收藏:0      [点我收藏+]

标签:提高效率   har   种类   地址   ons   test   操作符   fse   private   

从内存的角度考虑,不同情况下的C++类有什么区别呢?下面从空类、具有不同变量/函数、具有静态变量、继承、多态、虚拟继承等情况分析C++对象的内存空间大小和内存布局。本文讨论没有继承的情况,下一篇讨论有继承的情况
如无特别说明,本文代码均在64位机器上的VS2019运行。

无继承

一、内存空间占用情况

空类

class Test {

};

输出sizeof(Test)得到的结果为1。
空类的大小并不是0。这是因为空类也可以被实例化,而且它的每个实例也和其他实例一样在内存中拥有独一无二的地址。因此编译器会给空类加一个字节,这样在实例化时就可以给它的实例分配内存地址了。

有函数的类

class Test {
public:
    //一般需要定义构造函数、复制构造函数和重载操作符时,类是有成员变量的,
    //但这里仅为测试类的函数的内存占用情况,因此不设成员变量,且让函数为空
    Test() {} //constructor
    Test(const Test& t) {} //copy constructor
    void func(){ //inline function
        cout<<"hey hacker\n";
    }
    void func2(); //non-inline function
    Test& operator =(const Test& t){} //operator
    ~Test() {} //destructor
};

void Test::func2() {
    cout << "haha\n";
}

输出sizeof(Test)得到的结果为1。
我们测试了各种各样的非静态函数,最终输出结果表明它们都不占用对象的内存空间。事实也确实如此,C++对象模型中,函数不占用对象的内存。这是因为调用函数只需要知道函数的地址即可,而函数与类的实例无关。
这说明将函数封装并不需要额外的成本,C++在布局和存取时间上主要的额外负担是由virtual引起的。(下一篇会讨论有virtual的情况)

有成员变量的类

先介绍一个叫做padding的概念:为实例填充字节,使其大小成为某个数的整数倍(比如一个int和一个char一共占5字节,这时会再加上3字节的padding使其大小为8字节)。这通常是为了提高效率,具体实现取决于编译器。
这时就出现了两种情况:有padding和没有padding。
成员变量只有一种类型时没有padding

class Test {
public:
    char c1;
    char c2;
};//sizeof(Test) = 2*1(char) = 2 bytes

输出sizeof(Test)结果为2,说明实例没有被填充其他字节。
成员变量有多种时有padding

class Test {
public:
    int a;
    char c;
};//sizeof(Test) = 4(int) + 1(char) + 3(padding) = 8 bytes

输出sizeof(Test)结果为8,说明填充了3字节。

有静态成员变量的类

class Test {
public:
    static int a;
    static void func() {}
};

输出sizeof(Test)结果为1。这是因为静态成员变量只与类有关而与实例无关;C++对象模型中,静态成员不占用对象的内存空间。

二、成员在内存中的顺序

概念介绍:access section包括public, private和protected三种段落,如果一个类的定义中有2个public和1个private,那么它就有3个access sections。

同一access section内

C++ Standard要求,在同一个access section内,只要较晚出现的成员在类对象中有较高的地址即可。因此成员在内存中未必是连续的,中间可能会被填充一些字节(上面提到的padding);也可能会有编译器合成的一些内部使用的成员(data member),以支持整个对象模型(比如指向虚函数表或虚拟继承中指向父类的指针)。
代码测试如下:

class Point {
public:
    char x;
    char y;
    char z;
};
//main函数中:
printf("&Point::x = %p\n", &Point::x);//00000000
printf("&Point::y = %p\n", &Point::y);//00000001
printf("&Point::z = %p\n", &Point::z);//00000002

这里用的&Point::x是表示x在Point对象中的相对位置,即偏移量(offset),而&x是表示x的内存地址。这里输出成员变量的地址也可以,但输出偏移量会更直观一些。另外,注意这里要用printf,不要用cout。cout的输出都是1,无论位置和变量。
通过代码测试发现,x, y, z确实是按照声明顺序在内存中排列的。那么如果还有其他access section呢?

有多个access sections时

C++ Standard允许编译器将多个access sections中的成员自由排列而不必在乎它们在类中的声明顺序。但目前大部分编译器都是讲一个以上的access sections连锁在一起,依照声明顺序成为一个连续区块。而且access sections的数量不会带来额外负担,在3个public中声明3个int和在1个public中声明3个int,得到的对象大小是一样的。
代码测试如下:

class Point {
public:
    char x;
    char y;
private:
    char a;
public:
    char z;
    static void where_is_a() {
    printf("&Point::a = %p\n", &Point::a);
    }
};
//main函数中:
printf("&Point::x = %p\n", &Point::x);//00000000
printf("&Point::y = %p\n", &Point::y);//00000001
printf("&Point::z = %p\n", &Point::z);//00000003
Point::where_is_a();                  //00000002

可见编译器是按照各个成员的声明顺序将它们排列在了一起。

C++对象之内存(无继承)

标签:提高效率   har   种类   地址   ons   test   操作符   fse   private   

原文地址:https://www.cnblogs.com/saltedreed/p/11922532.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!