C++基础——sizeof 的使用

sizeof 的使用

sizeof是一个操作符,其作用是返回一个对象或类型所占的内存字节数;对象可以是各种类型的变量以及表达式。

  • 运算符,非函数;
  • 编译期求值,所以 sizeof 的结果是常量;
  • 求的是类型的长度,表达式是不需要求值的!

#define sizeof(type) ((size_t) ((type*)0 + 1))  //实现的原理就是利用指针的步进值

???

此处理解: sizeof(++i) 中 i 值为什么没有 +1

???

1. 基本数据类型的使用

对于内置数据类型(如:short,int,long,double,float等),它们的内存大小是和系统相关的,为了程序的跨平台使用,一般 通过sizeof来获取基本数据类型占用的内存大小。

2. 结构体的使用

结构体字节对齐的细节和编译器有关,但是一般而言满足一下准则:

  • 结构体变量的首地址能够被其最宽基本数据类型的大小整除;
  • 结构体的每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间家上填充字节;
  • 结构体的总大小为结构体最宽基本数据类型成员大小的整数倍,如有需要,编译器会在最末一个成员后加上填充字节。
struct S1  
{  
    char a;  
    int b;  
};  
  
struct S2  
{  
    int b;  
    char a;  
};  
  
struct S3  
{  
};  

int main()
{
    sizeof(S1); //值为8,字节对齐,在char之后会填充3个字节。
    sizeof(S2); //值为8,字节对齐,在char之后会填充3个字节。    
    sizeof(S3); //值为1,空结构体也占内存
    return 0;
}

字节对齐

许多计算机系统对基本数据类型合法地址做出了一些限制,要求某种类型对象的地址必须是某个值K(通常是2,4或8)的倍数。这种对齐限制简化了形成处理器和存储器系统之间的接口的硬件设计。对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。

需要字节对齐的根本原因在于CPU访问数据的效率问题。例如,假设一个处理器总是从存储器中取出8个字节,则地址必须为8的倍数。如果我们能保证将所有的double类型数据的地址对齐成8的倍数,那么就可以用一个存储器操作来读或者写值了。否则,我们可能需要执行两次存储器访问,因为对象可能被分放在两个8字节存储块中。另外,假设一个整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据;如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。

不同编译器计算法不同

struct node{
    double a;
    int b;
    int c;
    char d;
}

    同样X86架构上:vc编译后sizeof(node)=24;linux gcc 编译后 sizeof(node)=20;因为:在VC中规定, 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;而在gcc中规定对齐模数最大只能是4,也就是说,即使结构体中有double类型,对齐模数还是4,所以数据是按照1,2,4对齐的。所以:

vc下 :sizeof(node)=a(8)+b(4)+c(4)+{d(1)+7} = 24;
linux gcc下: sizeof(node)= a(4)+a(4)+b(4)+c(4)+{d(1)+3} = 20;

3. 联合体的使用

联合体在内存组织上是重叠式的,各成员共享一段内存,所以整个联合体的sizeof也就是所有成员中sizeof的最大值。

union test  
{  
    int a;  
    float b;  
    double c;  
    char d;  
};  
  
sizeof(test); //8

4. 数组的使用

数组的sizeof值等于数组所占用的内存字节数。

void func(char a[10])
{
    int size = sizeof(a);        //4
}

int main()
{
    char a[10];
    char str[] = "helloworld";
    
    cout << sizeof(a) << endl;    //10
    cout << sizeof(str) << endl;    //11,包含结尾符
}



当字符数组表示字符串时,其sizeof值将包含结尾符‘/0’;

当数组为形参时,其sizeof值相当于指针的sizeof。

5. 指针的使用

指针是用来记录另一个对象的地址,所以指针的内存大小当然就等于计算机内部地址总线的宽度;在32位计算机中,一个指针变量的返回值必定是4。

char *b = "helloworld";  
char *c[10];  
double *d;  
int **e;  
void (*pf)(); 


cout<<"sizeof(b)="<<sizeof(b)  << endl;      //指针指向字符串,值为4  
cout<<"sizeof(*b)="<<sizeof(*b) << endl;     //指针指向字符,值为1  
cout<<"sizeof(d)="<<sizeof(d)  <<endl;       //指针,值为4  
cout<<"sizeof(*d)="<<sizeof(*d) <<endl;      //指针指向浮点数,值为8  
cout<<"sizeof(e)"<<sizeof(e)<<endl;          //指针指向指针,值为4  
cout<<"sizeof(c)"<<sizeof(c)<<endl;          //指针数组,值为40  
cout<<"sizeof(pf)="<<sizeof(pf)<<endl;       //函数指针,值为4 

6. 函数的使用

sizeof也可对一个函数调用求值,其结果是函数返回值类型的大小。

  • 不可以对返回值类型为空的函数求值。 
  • 不可以对函数名求值。
  • 对有参数的函数,在用sizeof时,须写上实参表。

7. 类的使用

 空类的大小为1字节

class A {};

cout << "sizeof(A)= " << sizeof(A) << endl; //空类的大小为1

成员函数、静态数据成员都是不占用类对象的存储空间。

class B {
public:
	int i;
};

class B1 {
public:
	static int j;
	static int i;
	static int k;
};

class B2 {
public:
	void add() {}
	void sub() {}
};


cout << "sizeof(B)= " << sizeof(B) << endl;      //4
cout << "sizeof(B1)= " << sizeof(B1) << endl;    //1 : 静态成员变量不占用类的大小
cout << "sizeof(B2)= " << sizeof(B2) << endl;    //1 :成员函数不占用类的大小

包含虚函数的类,会存在一个虚指针

class B2 {
public:
	void add() {}
	void sub() {}
};

class C {
public:
	virtual void add() {}
};

class C1 {
public:
	virtual void add() {}
	virtual void sub() {}
};



cout << "sizeof(B2)= " << sizeof(B2) << endl;    //1 :成员函数不占用类的大小	
cout << "sizeof(C)= " << sizeof(C) << endl;      //4 : 虚指针的大小
cout << "sizeof(C1)= " << sizeof(C1) << endl;    //4 :只有一个虚指针

普通继承时派生类继承了所有基类的函数与成员

class D {
public:
	char c;
	int k;
};


class D2 : public D {
public:
	short a;
	long b;
};


cout << "sizeof(D)= " << sizeof(D) << endl;// 8 :字节对齐
cout << "sizeof(D2)= " << sizeof(D2) << endl;// 16: 包含基类的数据大小

包含虚函数类被继承时会继承其虚指针

class E {
public:
	virtual void add() {}
};

class E1 : public E{
public:
	
};


cout << "sizeof(E)= " << sizeof(E) << endl;//4
cout << "sizeof(E1)= " << sizeof(E1) << endl;//4:继承虚指针

虚继承时子类会继承基类的vptr。

class E {
public:
	virtual void add() {}
};


class F {
public:
	virtual void sub() {}
};

class F1 : virtual public E, virtual public F {
public:
	virtual void func() {}
};


cout << "sizeof(E)= " << sizeof(E) << endl;//4
cout << "sizeof(F)= " << sizeof(F) << endl;//4


/*
因为是虚继承,因此引入一个指针指向虚继承的基类,第二由于在基类中有虚函数,因此需要指针指向其虚函数表,由于派生类自己本身也有自己的虚函数
*/

cout << "sizeof(F1)= " << sizeof(F1) << endl;//16

不同编译器下的虚继承

1、对虚继承层次的对象的内存布局,在不同编译器实现有所区别。

首先,说说GCC的编译器.

它实现比较简单,不管是否虚继承,GCC都是将虚表指针在整个继承关系中共享的,不共享的是指向虚基类的指针。

class A {

    int a;

    virtual ~A(){}

};

class B:virtual public A{

    virtual void myfunB(){}

};

class C:virtual public A{

    virtual void myfunC(){}

};

class D:public B,public C{

    virtual void myfunD(){}

};

  以上代码中sizeof(A)=8,sizeof(B)=12,sizeof(C)=12,sizeof(D)=16.

  解释:A中int+虚表指针。B,C中由于是虚继承因此大小为A+指向虚基类的指针,B,C虽然加入了自己的虚函数,但是虚表指针是和基类共享的,因此不会有自己的虚表指针。D由于B,C都是虚继承,因此D只包含一个A的副本,于是D大小就等于A+B中的指向虚基类的指针+C中的指向虚基类的指针。

如果B,C不是虚继承,而是普通继承的话,那么A,B,C的大小都是8(没有指向虚基类的指针了),而D由于不是虚继承,因此包含两个A副本,大小为16.注意此时虽然D的大小和虚继承一样,但是内存布局却不同。

然后,来看看VC的编译器

  vc对虚表指针的处理比GCC复杂,它根据是否为虚继承来判断是否在继承关系中共享虚表指针,而对指向虚基类的指针和GCC一样是不共享,当然也不可能共享。

  代码同上。

  运行结果将会是sizeof(A)=8,sizeof(B)=16,sizeof(C)=16,sizeof(D)=24.

  解释:A中依然是int+虚表指针。B,C中由于是虚继承因此虚表指针不共享,由于B,C加入了自己的虚函数,所以B,C分别自己维护一个虚表指针,它指向自己的虚函数。(注意:只有子类有新的虚函数时,编译器才会在子类中添加虚表指针)因此B,C大小为A+自己的虚表指针+指向虚基类的指针。D由于B,C都是虚继承,因此D只包含一个A的副本,同时D是从B,C普通继承的,而不是虚继承的,因此没有自己的虚表指针。于是D大小就等于A+B的虚表指针+C的虚表指针+B中的指向虚基类的指针+C中的指向虚基类的指针。

  同样,如果去掉虚继承,结果将和GCC结果一样,A,B,C都是8,D为16,原因就是VC的编译器对于非虚继承,父类和子类是共享虚表指针的。

测试代码:

#include <iostream>
using namespace std;

class A {};

class B {
public:
	int i;
};

class B1 {
public:
	static int j;
	static int i;
	static int k;
};

class B2 {
public:
	void add() {}
	void sub() {}
};

class C {
public:
	virtual void add() {}
};

class C1 {
public:
	virtual void add() {}
	virtual void sub() {}
};


class D {
public:
	char c;
	int k;
};

class D1 {
public:
	short a;
	long b;
};

class D3 {
public:
	D d;
	char c;
};

class D2 : public D {
public:
	short a;
	long b;
};


class E {
public:
	virtual void add() {}
};

class E1 : public E{
public:
	
};

class F {
public:
	virtual void sub() {}
};

class F1 : virtual public E, virtual public F {
public:
	virtual void func() {}
};

struct node {
	double a;
	int b;
	int c;
	char d;
};


int main() 
{

	char *b = "helloworld";
	char *c[10];
	double *d;
	int **e;
	void(*pf)();


	cout << "sizeof(node)=" << sizeof(node) << endl;//24:  vc x86 

	cout << "sizeof(b)=" << sizeof(b) << endl;      //指针指向字符串,值为4  
	cout << "sizeof(*b)=" << sizeof(*b) << endl;     //指针指向字符,值为1  
	cout << "sizeof(d)=" << sizeof(d) << endl;       //指针,值为4  
	cout << "sizeof(*d)=" << sizeof(*d) << endl;      //指针指向浮点数,值为8  
	cout << "sizeof(e)" << sizeof(e) << endl;          //指针指向指针,值为4  
	cout << "sizeof(c)" << sizeof(c) << endl;          //指针数组,值为40  
	cout << "sizeof(pf)=" << sizeof(pf) << endl;       //函数指针,值为4 

	//测试平台x86
	cout << "sizeof(A)= " << sizeof(A) << endl; //空类的大小为1

	cout << "sizeof(B)= " << sizeof(B) << endl;//4
	cout << "sizeof(B1)= " << sizeof(B1) << endl;//1 : 静态成员变量不占用类的大小
	cout << "sizeof(B2)= " << sizeof(B2) << endl;//1 :成员函数不占用类的大小
	
	cout << "sizeof(C)= " << sizeof(C) << endl;//4 : 虚指针的大小
	cout << "sizeof(C1)= " << sizeof(C1) << endl;// 4:只有一个虚指针

	cout << "sizeof(D)= " << sizeof(D) << endl;// 8 :字节对齐
	cout << "sizeof(D1)= " << sizeof(D1) << endl;// 8: long 占4个字节
	cout << "sizeof(D2)= " << sizeof(D2) << endl;// 16: 包含基类的数据大小
	cout << "sizeof(D3)= " << sizeof(D3) << endl;//12

	cout << "sizeof(E)= " << sizeof(E) << endl;//4
	cout << "sizeof(E1)= " << sizeof(E1) << endl;//4:继承虚指针

	cout << "sizeof(E)= " << sizeof(E) << endl;//4
	cout << "sizeof(F)= " << sizeof(F) << endl;//4
	cout << "sizeof(F1)= " << sizeof(F1) << endl;//16

	return 0;
}

输出结果(visual studio x86):

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中,获取字符串的长度可以使用以下四种方法: 1. `sizeof()`函数 在C++中,`sizeof()`函数用于获取数据类型或变量所占用的内存大小,单位为字节。因此,可以使用`sizeof()`函数来获取字符串的长度,即字符串所占用的内存大小,包括字符串末尾的空字符('\0')。例如: ```c++ char str[] = "Hello"; int len = sizeof(str) / sizeof(char); // len = 6 ``` 需要注意的是,`sizeof()`函数返回的是编译时确定的值,因此对于动态分配的字符串,这种方法并不适用。 2. `size()`函数 在C++中,`size()`函数用于获取STL容器(如`string`)的大小,即容器中元素的个数。对于字符串,可以使用`size()`函数来获取其长度。例如: ```c++ string str = "Hello"; int len = str.size(); // len = 5 ``` 需要注意的是,`size()`函数返回的是容器中元素的个数,对于字符串来说,即字符串的长度。但是,`size()`函数并不包括字符串末尾的空字符('\0')。 3. `strlen()`函数 在C++中,`strlen()`函数用于获取C风格字符串的长度,即以空字符('\0')结尾的字符数组的长度。对于字符串,可以使用`strlen()`函数来获取其长度。例如: ```c++ char str[] = "Hello"; int len = strlen(str); // len = 5 ``` 需要注意的是,`strlen()`函数并不包括字符串末尾的空字符('\0')。 4. `length()`函数 在C++中,`length()`函数是`string`类的成员函数,用于获取字符串的长度。与`size()`函数功能类似,`length()`函数返回的是字符串的长度,不包括字符串末尾的空字符('\0')。例如: ```c++ string str = "Hello"; int len = str.length(); // len = 5 ``` 需要注意的是,`length()`函数只能用于`string`类,不能用于C风格字符串。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值