C/C++中数据大小、字节对齐、内存占用总结

数据大小获取

 
sizeof()操作符,求占用空间,对数组而言大小为 type大小 * 数据个数;
int a[] = {1,2,3,4,5}
sizeof(a) = 20
sizeof(&a) = 4
特殊的:在string时,由于字符串本质上就是自带‘\0’结尾的 char[] 数组,而char 的大小有恰好为1,
因此sizeof()就正好会是数组的长度;
const char a[] = "abcdefghi";
cout << sizeof(a) << endl; // 10
在C++98中是不允许对类的非静态成员变量使用sizeof()的,而在C++11中是合法的;
根据 C99 规定,sizeof是一个编译时刻就起效果的运算符,运行时在其内的任何运算都没有意义;
int i = 10;
sizeof(i++); // 4 此时i=10, 并不会自加,里面的运算是无意义的;
int size = sizeof(Volume) / sizeof(Volume[0]); // Volume[0]并不起作用,只看其类型


strlen() 在对字符串计算时 以遇到'\0'结束符为准,
不包含末尾的'\0' 字符(或内存中的内容为0)的长度的;
.length() (容器)字符串的长度,不包含结尾的'\0';
.size() (容器)字符串的长度,同样是不包含结尾的'\0';

strcpy() 拷贝函数其实是从指定位置覆盖,再加上‘\0’,而用%s输出就到'\0'为止。
会在 最后自动 添加 '\0' 作为结束符,,

--------------------------------------------------------------------------------------
例题1:
    char dog[]="wang\0miao";
    cout << sizeof(dog) << endl;// 10
    cout << strlen(dog) << endl;// 4 
例题2    
    char str[] = "ab\012\\n";
    printf("%d \n", sizeof(str) );//6
    printf("%d \n", strlen(str) );//5
解释例题2 :
\012 \0表示8进制(后面跟 <8 的数据的话) 此处表8进制的12也即十进制的10,
也即ASIIC符号 \n ,同时\0也可表示字符串结束;
\\ 表示 斜杆;
最终字符为 a b \n \n \0
注:\ 表示转义字符标志;

例3
char c1[] ={'a', 'b', '\0', 'd', 'e'};
char c2[] ="hello";
sizeof(c1) strlen(c1); // 5 2
sizeof(c2) strlen(c2); // 6 5

例4
char *str1 ="hello";
char str2[] = "hello";
sizeof(str1);strlen(str1); // 4, 5(32位OS上,在64位OS上为:8, 5)
sizeof(str2);strlen(str2); // 6, 5
区别:
str1是const char指针变量,指向存放在全局区的字符常量"hello"的首地址,
str2[]定义的是数组,并将字符串常量赋值给他进行初始化;
注:sizeof(string)问题
string的实现在各库中可能有所不同,但是在同一库中相同一点是,无论你的string里放多长的字符串,它的sizeof()都是固定的,字符串所占的空间是从堆中动态分配的,与sizeof()无关。其大小跟编译器有关,VC6.0测试后sizeof(string)=16.  DEV C++中为4。
 
例题:题目来源--联发科笔试
char str1[] = {'a', 'b', 'c', 'd', 'e', '\0', 'f'};
char str2[] = "abcde";
char str3[][80] = {"C++", "JAVA", "C", "PYTHON"};
const char *ptr = "abcde";
	
printf("sizeof(str1)=%d, strlen(str1)=%d\n", sizeof(str1), strlen(str1)); 	// 7 5
printf("sizeof(str2)=%d, strlen(str2)=%d\n", sizeof(str2), strlen(str2)); 	// 6 5
printf("sizeof(str3)=%d, strlen(str3)=%d\n", sizeof(str3), strlen(str3)); 	// 320 3
printf("sizeof(ptr)=%d, strlen(ptr)=%d\n", sizeof(ptr), strlen(ptr)); 		// 4 5

【注】
第三条输出语句strlen(str3)在我的IDE上报错,据说在Linux上可以通过,,大家可以试试下,,结果请给出评论,谢谢!!!
第四条输出语句sizeof(ptr)在64位OS上输出8;

 

 
 

字节对齐


需要字节对齐的根本原因在于CPU访问数据的效率问题。不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间
 
#pragma pack(n) // 指定内存对齐方式,往后的的代码什么的不按自身宽度对齐而是按指定宽度对齐;
#pragma pack() //  取消用户自定义字节对齐方式

#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

 __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。
如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
 __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。


GCC默认按4字节对齐,

对于标准数据类型,地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:

数组:按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。

枚举:只占4个字节;

联合体:大于等于其成员中最宽的成员,且是其他成员变量基本类型的整数倍;

结构体:结构体中数据成员都要对齐,且结构体整体也需要对齐(整体对齐为数据成员最大基本类型的整数倍);

        :看下文;

 
 
 

内存占用


1、枚举eunm

对于enum变量,指一个被命名的整型常数的集合,本质上是一组(int型)常数,只占固定的 4 个字节;
定义格式:
    enum 枚举名 {枚举元素1, 枚举元素2, ……};
注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。

enum season {spring, summer=3, autumn, winter};
没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,
autumn 的值为 4,winter 的值为 5

 

2、联合体union

所有成员相对于基地址均为0,共享一段内存,并且同一时间只能储存其中一个成员变量的值(即后面的赋值将 覆盖前面的赋值)空间需足够宽(大于等于其成员中 最宽的成员)且大小能被其包含的所有基本数据类型的大小所整除。
// GCC 默认 4字节对齐
union test{
    char s[9];      // 9
    int  a;         // 4
    double b;       // 8
};
sizeof( test );   //>=最宽数据成员9, 且同时是其他类型char(1)、(int)4和double(8)的整数倍, 因此为16

union test{
    char s[8];      // 8
    int  a;         // 4
    double b;       // 8
};
printf("%d\n", sizeof( test ) );
 // >=最宽数据成员8, 且同时是其他类型char(1)、(int)4和double(8)的整数倍, 因此为8

仔细看上下两部分代码的区别哦!!!

 

 

3、结构体struct

结构体各成员根据定义依次申请内存空间,其中 包含 数据成员自身对齐和 结构体本身的对齐整体对齐 为数据成员中最大基本类型的整数倍);
struct struct_test_1{
    char a;        // 1
    int b;         // 4
    double c;      // 8
}test1;
sizeof( test1 );   // 16 

struct struct_test_2{
    char a;          // 1
    double     b;    // 8
    int c;           // 4
    static int d;    // 存放在静态数据区,sizeof() 不计static所占用空间
}test2;
sizeof( test2 )      // 24
对struct_test_2分析:
    首先char a 申请一个字节空间,开辟首地址,之后double b存入; 它会认为内存是以自己的大小(double = 8 byte)来划分,
因此元素放置在自身宽度的整数倍开始(原则一),故b不从偏移1开始,因为不是整数倍,故中间填充7字节,此时消耗16字节,
再开辟int c,4 字节,是从4字节的整数倍开始,故消耗了20字节。此时存储单元不是最宽元素(8bytes)的整数倍,
故按最宽元素整数倍对齐(原则二);一共消耗24 字节。

struct Data
{
    int a;          // 4
    float b;        // 4 
    double c;       // 8
    char d[21];     // 21
}data;    
printf("%d\n", sizeof(data) ); // 40 整体对齐为  数据成员最大基本类型(double)的整数倍



有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。
例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。
正是基于这种考虑,C语言又提供了一种叫做 位域 的数据结构。位域本质上是一种结构类型,
不过其成员是按二进制位分配的。并且其类型必须是int型,这就包括有符号和无符号的。

在结构体或是联合体定义时,我们可以指定某个成员变量所占的用的二进制(bit)位数,这就是位域。
位域的使用和结构体成员的使用相同,其一般形式为:
变量类型 位域变量名:位域  其中(位域变量名为可选项,)
    struct bs{
        int a:8;
        int b:2;
        int c:5;
    };
    printf("%d ", sizeof(struct bs) ); // 4
位域使用规则如下:
1、位域的宽度不能超过它所依附的数据类型的长度, 比如:unsigned int,长度为 4 个字节,数字就不能超过 32
2、位域可以无位域名,这时它只用来作填充或调整成员位置。因为没有名称,无名的位域是不能使用的。

位域存储规则如下:
1、依然遵循struct的 对齐规则;

2、当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,
直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。
    struct bs{
        int a:26;
        int b:2;
        int c:5;
    };
    printf("%d ", sizeof(struct bs) ); // 8
3、如果成员之间穿插着非位域成员,那么不会进行压缩。
  struct bs{
        int a:26;
        int b;
        int c:5;
    };
    printf("%d ", sizeof(struct bs) ); // 12
4、当相邻成员的类型不同时,不同的编译器有不同的实现方案,GCC 会压缩存储,而 VC/VS 不会。

实例1

struct A{
  int a;//4
  short b;//2+2
  int c;//4
  char d;//1+3
}; 
struct B{
  int a; // 4
  short b; // 2
  char d;// 1 + 1
  int c; // 4
};

sizeof(A) = 16, sizeof(B) = 12

实例2

struct foo{
    char a; // 1 + 3
    int b[10]; // 4 * 10
    char c;// +1
};// 48
void main_2()
{
    struct foo f={'X', {10,20,30,40,50,60,70,80,90}, 'F'};
    
    cout << "单个成员变量值" << endl;
    printf("%c %d %c \n", f.a, f.b[0], f.c);
    // cout << f.a<< " " << f.b[0]<< " " << f.c<< endl; // f.b[0] 输出是有错误的
        
    cout << "相对偏移量" << endl;
    printf ("sizeof() = %d\n", sizeof(foo) );
    printf ("offsetof(struct foo,a) is %d\n",(int)offsetof(struct foo,a));//输出 0
    printf ("offsetof(struct foo,b) is %d\n",(int)offsetof(struct foo,b));//输出 1
    printf ("offsetof(struct foo,c) is %d\n",(int)offsetof(struct foo,c));//输出 11
    
    cout << "相对地址" << endl;
    printf("struct          address:0x%x\n",&f);
    printf("struct member a address:0x%x\n",&(f.a) );
    printf("struct member b address:0x%x\n",&(f.b) );
    printf("struct member c address:0x%x\n",&(f.c) );
    
    cout << "\n用地址操作单个成员变量值" << endl;
    char *p = (char *)(&f);
    // 由于存在 字节对齐问题  
    printf("地址:%#x 内容:%c \n", p, *p);
    printf("地址:%#x 内容:%d \n", p+4, *(p+4) );
    printf("地址:%#x 内容:%d \n", p+8, *(p+8) );
    printf("地址:%#x 内容:%c \n", p+44, *(p+44) );
}
 

4、C++类

(若其中包含上述的结构体、联合体等,仍需考虑内存对齐问题

 

a、空类占一个字节

class A{
    
};
cout << "sizeof(A) "<< sizeof(A) << endl;// 1
 
b、类(class和结构体struct是有异曲同工之妙的)的数据成员所占内存按struct的计算方式计算(访问权限属性不影响存储,考虑到包含其他如struct、emun、union等复杂结构仍需先按自身类型对齐,在按class的算),其中static数据不是对象属性,而是类属性,其存储在数据区,sizeof 不计算其空间;
class A{
    int a;
    double c;
    char b;
    union{
        int aa;
        char bb[9];
        double cc;
    }test;
};
cout << "sizeof(A) "<< sizeof(A) << endl;// 40 = 4 + 4 + 8 + 1 + 7 + 16 
 
c、不管是static还是非static成员函数,非虚成员函数均不占空间
class B{
public:
    void func1(){}
    void func2(){}
};
cout << "sizeof(A) "<< sizeof(A) << endl;// 1
 
d、 若存在虚函数,在类开辟的空间开始位置插入虚函数表( 虚函数vptr指针 ),
  所有同一个类的虚函数共享一个虚函数表,故一共占用4个字节
class B{
public:
    void func1(){}
    void func2(){}
     virtual void func3() {}
     virtual void func4() {}
};
cout << "sizeof(A) "<< sizeof(A) << endl;// 4
 
e、继承时,在内存中,派生类所在位置在基类前面;
非虚继 情况下,不管基类存不存在虚函数,派生类会和基类均会共同使用一个虚函数表,
虚继 情况下,基类存在不存在虚函数时,派生类的虚函数会共同使用派生类自己的虚函数表
class B     
{  
private:
    char ch;     
    virtual void func0()  {  }   
};
class BB: public B  
{     
public:
    int e;     
    virtual void func0()  {  }   
    virtual void func1()  {  }  
};
class BB1: virtual public B  // 虚继承
{     
public:
    int e;     
    virtual void func0()  {  }   
    virtual void func1()  {  }  
};

      cout<< "sizeof(B)" << sizeof(B) <<endl; // 8
//    B的内存分布
//    4字节 虚表指针
//    1字节 ch
//    3字节 补齐,因为最大的字段(虚表长度)size为4,所以结构体补全成4的倍数
    
    cout<< "sizeof(BB)"<< sizeof(BB) <<endl; // 12
    cout<< "sizeof(BB1)"<< sizeof(BB1) <<endl; // 16
 
 
总结1:
1、结构体或联合体的数据成员对齐规则:
第一个数据成员放在ofset为0的地方,以后每个数据成员的对齐按照#pragmapack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构体或联合体的整体对齐规则:
在数据成员完成各自对齐之后,结构或联合本身也要进行对齐,对齐将按照#pragmapack指定的数值和结构或联合最大数据成员长度中,比较小的那个进行。
 
 
总结2:
1、基类对象的存储空间 = 非static数据成员大小 + 4字节虚函数表空间(若存在虚函数);
 
2、派生类对象大小 = 基类对象大小 + 派生类独有的非static数据成员大小(注意复杂结构的字节对齐) (普通继承而非虚继承,派生类会与基类共享虚函数表);
 
3、虚继承(在继承方式中加关键字virtual)的存储空间 = 基类对象大小 + 派生类独有的非static数据成员大小(注意复杂结构的字节对齐) + 每个类的虚函数存储空间;
 
 
 

 
 
虚继承
 
在多继承时,如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。或者,  如果在多条继承路径上有一个公共的基类,那么在继承路径的某处  汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象。
  要使这个公共基类在派生类中只产生一个子对象, 必须对这个基类  声明为虚继承 ,使这个基类成为虚基类。
 
 
使用虚继承之后
 
 
 
 
 
 
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值