C/C++ sizeof() 详解(基本数据类型、指针、数组、字符串、函数、结构体、类、联合体)

sizeof() 介绍

sizeof 是一个关键字、操作符,也是一个编译时运算符,其作用是返回一个对象或者类型所占的内存字节数

sizeof 运算符可用于获取类、结构、共用体和其他用户自定义数据类型的大小。

详解C++ sizeof
c++中sizeof()的用法介绍

sizeof() 的使用方法

1sizeof(object); 	//sizeof(对象);2sizeof(type_name); 	//sizeof(类型);3sizeof object; 		//sizeof对象;【不常用】

1. sizeof 计算基本数据类型和表达式

sizeof计算对象的大小实际上是转换成对象类型进行计算,也就是说,同种类型的不同对象其sizeof值都是一致的。

这里,对象可以进一步延伸至表达式,即sizeof可以对一个表达式求值,编译器根据表达式的最终结果类型来确定大小,sizeof是编译时进行运算,与运行时无关,不会对表达式进行计算

#include <iostream>
#include <string>
using namespace std;
int main() {
	cout<<"sizeof(bool)="<<sizeof(bool)<<endl;				// 1	
    cout<<"sizeof(char)="<<sizeof(char)<<endl;				// 1	
    cout<<"sizeof(short)="<<sizeof(short int)<<endl;		// 2
    cout<<"sizeof(int)="<<sizeof(int)<<endl;				// 4
    cout<<"sizeof(long)="<<sizeof(long int)<<endl;			// 4
    cout<<"sizeof(long long)="<<sizeof(long long)<<endl;	// 8
    cout<<"sizeof(float)="<<sizeof(float)<<endl;			// 4
    cout<<"sizeof(double)="<<sizeof(double)<<endl;			// 8
    cout <<"sizeof(string)="<<sizeof(string) << endl;		// 24 string的实现在各库中可能有所不同
    int i = 8;
    cout<<"i="<<i<<endl;									// i = 8
    cout<<"sizeof(i)="<<sizeof(i)<<endl;					// 4
    cout<<"sizeof(i=5)="<<sizeof(i=5)<<endl;				// 4
    cout<<"i="<<i<<endl;									// i = 8
    return 0;
}

由于基本数据类型的内存大小是和系统相关的,所以在不同的系统下取值可能不同。

2. sizeof 计算指针变量

指针记录了一个对象的地址。指针变量的位宽等于机器字长,机器字长由CPU寄存器位数决定。在32位系统中,一个指针变量的返回值为4字节,64位系统中指针变量的sizeof结果为8字节。

#include <iostream>
#include <string>
using namespace std;

char testfunc(){
    return 'k';
}

int main() {
    char* pc = "abc";
    int* pi=new int[10];
    string* ps;
    char** ppc = &pc;
    void (*pf)();       // 函数指针
    sizeof( pc );       // 结果为4
    sizeof( pi );       // 结果为4
    sizeof( ps );       // 结果为4
    sizeof( ppc );      // 结果为4
    sizeof( pf );       // 结果为4
    sizeof( &testfunc); // 结果为4
    sizeof( testfunc()); // 结果为1
    sizeof((*testfunc)()); // 结果为1
    return 0;
}

(1)指针变量的sizeof值与指针所指的对象类型没有任何关系,与指针申请多少空间没有关系,所有的指针变量所占内存大小均相等。那为什么在本机64bits系统下,指针变量大小仍然是4个字节,因为使用32位编译器编译得到程序是32位,故指针大小是4字节,可自行修改编译器版本,不再赘述。

(2)&testfunc代表一个函数指针,指针大小是4,所以sizeof(&testfunc)==4。testfunc()代表一次函数调用,返回值类型是char,所以sizeof(testfunc())==sizeof(char)==1。testfunc名本身就是一个函数指针,所以(*testfunc)()也是一次函数调用,sizeof((*testfunc)())==sizeof(char)==1

4. sizeof 计算数组

当sizeof作用于数组时,求取的是数组所有元素所占用的大小

#include <iostream>
using namespace std;

void foo(int a[3]){		// a以指针传递
    cout << sizeof(a) << endl;
}

int main() {
    int A[3][5];				// 二维数组
    char c[] = "123456";		// 字符串
    char *ch[10];				// 指针数组
    double*(*d)[3][6];

    cout<<sizeof(A)<<endl;      // 输出60 = 4 * 3 * 5
    cout<<sizeof(A[4])<<endl;   // 输出20 = 4 * 5
    cout<<sizeof(A[0][0])<<endl;// 输出4
    
    cout<<sizeof(c)<<endl;      // 输出7 = 6 + 1 ('')
    
    cout<<sizeof(ch)<<endl;     // 输出40,长度是10的指针数组,4 * 10
    cout<<sizeof(*ch)<<endl;    // 输出4,指针数组中的第一个元素(也是指针)
    cout<<sizeof(**ch)<<endl;   // 输出1,指向字符

    cout<<sizeof(d)<<endl;      // 输出4
    cout<<sizeof(*d)<<endl;     // 输出72 = 4 * 3 * 6
    cout<<sizeof(**d)<<endl;    // 输出24 = 4 * 6
    cout<<sizeof(***d)<<endl;   // 输出4
    cout<<sizeof(****d)<<endl;  // 输出8

	int nums[] = {1, 2, 3};
    cout << sizeof(nums) << endl;	// 12
    foo(nums);						// 4
    // void foo(int a[3]){			// a以指针传递
    //     cout << sizeof(a) << endl;
    // }
    return 0;
}

(1)A的数据类型是int[3][5]A[4]的数据类型是int[5]A[0][0]数据类型是int
尽管A[4]下标越界,但sizeof运算只关心数据类型,在编译阶段已经完成,不会造成运行错误。

sizeof(A)== sizeof(int[3][5]) == 3*5*sizeof(int) == 60
sizeof(A[4]) == sizeof(int[5]) == 5*sizeof(int) == 20	
sizeof(A[0][0]) == sizeof(int) == 4

(2)由于字符串以空字符 '' 结尾,所以 c 的数据类型是 char[7],所以sizeof(c)=sizeof(char[7])==7

(3)指针数组sizeof() = 指针内存大小 * 元素个数

(4) 多级指针,d是一个指针,不管它指向的对象是什么数据类型,自身大小永远是4,所以sizeof(d)==4。

double*(*d)[3][6];
sizeof(d) == 4;
sizeof(*d) == sizeof(double*[3][6]) == 3*6*sizeof(double*) == 18*4 == 72
sizeof(**d)== sizeof(double*[6]) == 6*sizeof(double*) == 24
sizeof(***d) == sizeof(double*) == 4
sizeof(****d) == sizeof(double) == 8

(5)当数组作为函数形参时,以指针类型进行传递,因此 i = 4

5. 字符串 的 sizeof() 与 strlen()

#include <iostream>
#include <string>
#include <cstring>
using namespace std;

int main() {
    char* strPtr = "hello";
    char strs[] = "hello";
    string str = "h";
    cout << sizeof(strPtr) << endl;     // 4
    cout << sizeof(strs) << endl;       // 6
    cout << sizeof(str) << endl;        // 24(类型大小,应环境而异)
    
    cout << strlen(strPtr) << endl;     // 5
	cout << strlen(strs) << endl;       // 5
    return 0;
}

对于sizeof(),strPtr 是指针 (4),strs 是字符数组(包括末尾符号’’)(6),str 是string类型变量(24)
strlen(char *)则是直接返回实际字符串的字符个数(5)

6. sizeof 计算函数

sizeof() 也可对一个函数调用求值,其结果是函数返回值类型的大小,函数并不会被调用执行。

对函数求值的形式:sizeof(函数名(实参表))

注意:
1)不可以对返回值类型为空的函数求值。
2)不可以对函数名求值。
3)对有参数的函数,须写上实参表。

#include <iostream>
using namespace std;

int intfun(){ return 1; }

double doufun(int a, double b){ return a + b; }

void voidfun(){ }

int main(){
    // cout << sizeof(intfun) << endl;      // 编译失败
    cout << sizeof(intfun()) << endl;       // 函数返回int,4
    cout << sizeof(doufun(1, 1.5)) << endl; // 函数返回double,8;对有参数的函数,须写上实参表。
    cout << sizeof(voidfun()) << endl;      // 编译运行返回 1, 但提示 invalid application of 'sizeof' to a void type [-Wpointer-arith]
    return 0;
}

7. sizeof 计算结构体

sizeof作用于基本数据类型,在特定的平台和特定的编译器中,结果是确定的;

如果使用sizeof计算构造类型:结构体、联合体和类的大小时,情况稍微复杂一些。

对于结构体而言:

struct S1
{
	char c;
	int i;
};

cout<<sizeof(S1)=<< sizeof(S1) << endl;	// 8

sizeof(S1)结果是8,并不是想象中的sizeof(char) + sizeof(int)=5

这是因为结构体或类成员变量具有不同类型时,需进行成员变量的对齐。《计算机组成原理》一书中说明,对齐的目的是减少访存指令周期,提高CPU存储速度

内存对齐原则:

(1)结构体变量的首地址能够被其最宽基本成员类型大小所整除;

(2)结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);

(3)结构体的总大小为结构体最宽基本成员类型大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。

注意:空结构体(不含数据成员)的sizeof值为 1
试想一个“不占空间“的变量如何被取地址、两个不同的“空结构体”变量又如何得以区分
呢,于是,“空结构体"变量也得被存储,这样编译器也就只能为其分配一个字节的空间用于占位了。

静态成员存放在静态存储区,不占用结构体的大小。

成员函数不占用结构体大小。

struct st0{
    char a;     // 1 + pad(3)
    char* a1;   // 4
    int c;      // 4
};              // 12

struct st1{
   	char a;     // 1 + pad(7)
    double b;   // 8
    int c;      // 4
    char d;     // 1 + pad(3)
};              // 24

struct st2{
    char a;     // 1
    char a1;    // 1 + pad(2)
    int c;      // 4
    st1 st;     // 24
    char d;     // 1 + pad(7) 
};              // 40

// 获取成员在结构体的地址偏移量
cout << offsetof(st2, a) << endl;   // 0
cout << offsetof(st2, d) << endl;   // 32

在计算结构体大小时,要寻找结构体的最宽基本数据类型,包括其嵌套的结构体中的成员

比如 st2 中最宽数据类型为st1中的double类型,st1 在结构体 st2 也遵循内存对齐原则。

结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof(st, m)来获得,这个宏也在 stddef.h 中定义,如下:

#define offsetof(s,m) (size_t)&(((s *)0)->m)

例如获得 st1 中的偏移量,方法为

size_t pos = offsetof(S1, i); 	// pos等于4

8. sizeof 计算类

sizeof(类)

与类相比,结构体同样可以包含成员函数、构造函数、析构函数、虚函数和继承,但一般不这么使用,沿用了C的结构体使用习惯。类与结构体唯一的区别就是结构体的成员的默认权限是public,而类是private。

类是C++中常用的自定义构造类型,有数据成员和成员函数组成,进行 sizeof 计算时,和结构体类似

(1)空类大小为 1 。
(2)类的成员函数不占用结构体大小,类对象的大小是由它数据成员决定的。
(3)类和结构体一样,同样需要对齐,具体对齐的规则见上文结构体的内存对齐。
(4)类如果包含虚函数,编译器会在类对象中插入一个指向虚函数表的指针(多个虚函数也只有一个),以帮助实现虚函数的动态调用。
(5)静态成员存放在静态存储区,不占用类的大小。

class c0{
    char a;     // 1 + pad(3)
    char* a1;   // 4
    int c;      // 4
};              // 12

class c1{
    char a;     // 1 + pad(7)
    double b;   // 8
    int c;      // 4
    char d;     // 1 + pad(3)
};              // 24
             
class c2{
    char a;     // 1
    char a1;    // 1 + pad(2)
    int c;      // 4
    c1 cla;     // 24
    char d;     // 1 + pad(7) 
};              // 40

struct c3
{
    static int i;       // 静态成员
    int fun();          // 成员函数
    int d;              // 4
    char ch;            // 1 + pad(3)
    virtual int vir1(); // 8 (64bit)
};                      // 16


计算以下几个类的大小

class A {};
int main(){
  cout<<sizeof(A)<<endl;	// 输出 1;
  A a;
  cout<<sizeof(a)<<endl;	// 输出 1;
  return 0;
}

空类的大小是1, 在C++中 空类会占 1 个字节,这是为了让对象的实例能够相互区别。

空类的实例大小就是类的大小,所以 sizeof(a) =1 字节;如果 a 是指针,则 sizeof(a) 就是指针的大小,即4字节。

具体来说,空类同样可以被实例化,并且每个实例在内存中都有独一无二的地址,因此,编译器会给空类隐含加上一个字节,这样空类实例化之后就会拥有独一无二的内存地址。

当该空白类作为基类时,该类的大小就优化为0了,子类的大小就是子类本身的大小。这就是所谓的空白基类最优化。


class A { virtual int Fun(){} };
int main(){
  cout<<sizeof(A)<<endl;// 输出 4(32位机器)/8(64位机器);
  A a;
  cout<<sizeof(a)<<endl;// 输出 4(32位机器)/8(64位机器);
  return 0;
}

因为有虚函数的类对象中都(最多)有一个 虚函数表指针 __vptr,其大小是 4(32位机器) / 8(64位机器) 字节。


class A { 
	static int a; 
	void fun();
};
int main(){
  cout<<sizeof(A)<<endl;	// 输出 1;
  A a;
  cout<<sizeof(a)<<endl;	// 输出 1;
  return 0;
}

普通函数也不占用类大小。


class A { int a; int b;};
int main(){
  cout<<sizeof(A)<<endl;// 输出 8;
  A a;
  cout<<sizeof(a)<<endl;// 输出 8;
  return 0;
}
 
class A { static int a; int b; };
int main(){
  cout<<sizeof(A)<<endl;// 输出 4;
  A a;
  cout<<sizeof(a)<<endl;// 输出 4;
  return 0;
}

静态成员存放在静态存储区,不占用类的大小。



9. sizeof 计算联合体

结构体在内存组织上是顺序式的,共用体则是重叠式,各成员共享一段内存,所以整个共用体的sizeof也就是每个成员sizeof的最大值

结构体的成员也可以是构造类型,这里,构造类型成员是被作为整体考虑的

所以,下面例子中,假设sizeof(s)的值大于sizeof(i)和sizeof©,那么sizeof(U)等于sizeof(s)。

union U
{   
    int i;      // 4
    char c;     // 1
    c3 class3;  // 16
};              // 16