Bootstrap

C/C++基础之sizeof使用

在  中, 是一个判断数据类型或者表达式长度的运算符。

1  定义

 是  中的一个操作符(operator),返回一个对象或者类型所占的内存字节数。

The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type(including aggregate types). This keyword returns a value of type size_t.                                                                                              ——来自MSDN

其返回值类型为  ,在头文件  中定义为: ;

从 的定义可以看出: 不是一个函数,因为函数调用必须有一对括号。

#include 

int main(void)
{
   int num = 97;

   printf("sizeof(num = 0)的值:%d\n",sizeof(num = 0));   
   printf("num 的值:%d\n",num);   
   return 0;
}

运行结果为4,97;并不是4,0

说明: 不是标准意义上的一元操作符,不支持链式表达式, 作用域范围内的语句不会编译成机器码,如  中的  不执行。 也不是函数,  更像一个特殊的宏,在编译阶段求值。

2  用法

 有两种语法形式,如下:

sizeof(type_name);    //sizeof(类型);
sizeof (object);      //或sizeof object 都属于 sizeof对象;

所以:

int i;
sizeof(i);    //合理
sizeof i;     //合理
sizeof(int);  //合理
sizeof int;   //不合理
  • 对类型使用  时, 是非法的,必须写为 ;

  • 无论是对对象还是类型取值, 这种形式都是对的;

1 基本数据类型的 

这里的基本数据类型是指这样的简单内置数据类型。

由于它们的内存大小是和系统相关的,所以在不同的系统下取值可能不同。

#include 
using namespace std;
 
int main()
{
   cout << "Size of char : " << sizeof(char) << endl;
   cout << "Size of int : " << sizeof(int) << endl;
   cout << "Size of short int : " << sizeof(short int) << endl;
   cout << "Size of long int : " << sizeof(long int) << endl;
   cout << "Size of float : " << sizeof(float) << endl;
   cout << "Size of double : " << sizeof(double) << endl;
   cout << "Size of wchar_t : " << sizeof(wchar_t) << endl;
   return 0;
}

在 32 位系统下内置数据类型与其  运算结果如下:

Size of char : 1
Size of int : 4
Size of short int : 2
Size of long int : 4
Size of float : 4
Size of double : 8
Size of wchar_t : 4
  •  不影响内置类型  的取值

2 指针类型的 

指针主要用于存储地址,前几天文章提到过,指针变量的位宽等于机器字长,机器字长由 CPU 寄存器位数决定。在 32 位系统中,一个指针变量的返回值为 4 字节, 64 位系统中指针变量的  结果为 8 字节。

char *p =”hello”; 
sizeof( p );       // 结果为4 
sizeof(*p);        // 结果为1 
int *pi; 
sizeof( pi );      //结果为4 
sizeof(*pi);       //结果为4 
char **pp = &p; 
sizeof( pp );      // 结果为4 
sizeof( *pp );     // 结果为4 
  • 指针变量的  值与指针所指的对象类型没有任何关系,与指针申请多少空间没有关系,所有的指针变量所占内存大小均相等。

  • 如果使用 32 位编译器编译得到程序是 32 位,那么在 64bits 系统下,指针变量大小仍然是 4 个字节。

3 函数类型的 

函数类型以其返回类型作为自身类型,进行  取值。

void fun1()
{
}
int fun2()
{
   return 0;
}
double fun3()
{
   return 0.0;
}
cout << sizeof(fun1()) << endl;  //错误!无法对void类型使用sizeof
cout << sizeof(fun2()) << endl;  //fun2()返回值类型为int,输出4
cout << sizeof(fun3()) << endl;  //fun3()返回值类型为double,输出8
  • 注意:不能对返回  函数和函数指针进行  取值。

4 数组类型的 

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

 int A[3][5];
    char c[]="abcdef";
    double*(*d)[3][6];

    cout<

A 的数据类型是  , 的数据类型是 数据类型是  。所以:

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

如果字符数组表示字符串,数组末自动插入 '\0',所以 c 的数据类型是  ,所以 。

d 是一个很奇怪的定义,他表示一个指向  类型数组的指针。既然是指针,所以  就是4。

既然 d 是执行  类型的指针,  就表示一个  的多维数组类型,因此  。

 表示一个  类型的数组,所以 。

 表示其中的一个元素,也就是  ,所以  。

 是一个  ,所以 。

当数组作为函数形参时,下面输出结果应该是多少呢?

int GetStrLength(char str[])
{
   return sizeof(str);
}

int main()
{
   char szStr[] = "abcdef";
   cout<< GetStrLength() << endl;
   return 0;
}

输出不是 7 ,这里函数参数  已不再是数组类型,而是蜕变成指针,我们调用函数  时,程序会在栈上分配一个大小为 7 的数组吗?不会!数组是“传址”的,调用者只需将实参的地址传递过去,所以 str 自然为指针类型 (char*) ,输出值为:4 。

  • 数组的大小是各维数的乘积*数组元素的大小。

  • 向函数形参传递数组,数组将会退化为指针,失去原来数组的特性。

4 结构体类型的 

对于 struct 数据结构由 CPU 的对齐问题导致 struct 的大小变得比较复杂。具体可以查看以前的文章

理论上,int 占 4byte , char 占一个 byte ,那么将它们放到一个结构体中应该占 4+1=5byte ;但是实际上,通过运行程序得到的结果是 8byte 。

#include

struct{
    int x;
    char y;
}Test;

int main()
{
    printf("%d\n",sizeof(Test)); // 输出8不是5
    return 0;
}

结构体的大小跟结构体成员对齐有密切关系,而并非简单地等于各个成员的大小之和!比如对如下结构体两个结构体 A、B 使用  的结果分别是:16,24。可以看出 si 并不等于  。


struct A
{
  int num1;
  int num2;
  double num3;
};
struct B
{
  int num1;
  double num3;
  int num2;
};

结构体A和B中包含的成员都一样,只不过顺序不同而已,为什么其大小不一样呢?要解释这个问题,就要了解结构体成员对齐的规则。

  • 结构体的大小等于结构体内最大成员大小的整数倍

  • 结构体内的成员的首地址相对于结构体首地址的偏移量是其类型大小的整数倍,比如说 double 型成员相对于结构体的首地址的地址偏移量应该是 8 的倍数。

  • 为了满足规则 1 和 2 编译器会在结构体成员之后进行字节填充!

从三个规则我们来看看为什么  等于 24 :首先假设结构体的首地址为0,第一个成员 num1 的首地址是 0 (满足规则2),它的类型是 int ,因此它占用地址空间  。第二个成员 num3 是 double 类型,它占用 8 个字节,由于之前的 num1 只占用了 4 个字节,为了满足规则 2 ,需要使用规则 3 在 num1 后面填充 4 个字节(),使得 num3 的起始地址偏移量为 8 ,因此 num3 占用的地址空间是:。第三个成员 num2 是 int 型,其大小为 4 ,由于 num1 和num3 一共占用了 16 个字节,此时无须任何填充就能满足规则 2。因此 num2 占用的地址空间是  。那么是不是结构体的总大小就是  共 20 个字节呢?请注意,别忘了规则1!由于结构体内最大成员是 double 占用 8 个字节,因此最后还需要在 num2 后面填充 4 个字节,使得结构体总体大小为 24 。

struct S{ }; 
sizeof(S); // 结果为1
  • 对于一个空 struct 结构体取 sizeof 运算,运算结果为 1 并非 0 。因为编译器为保证此空 struct 存在,专门分配一个字节。

  • 如果存在结构体嵌套,无论内层还是外层均需要采用内存对齐。

5 类的 

在这种情况下,只需要考虑对齐方式即可。

class A 
{ 
  public: 
  int b; 
  float c; 
  char d; 
};
class B
{ 
};

int main(void) 
{ 
  cout << “sizeof(A) is ” << sizeof(A) << endl; 
  //输出结果为12
  cout << “sizeof(B) is ” << sizeof(B) << endl; 
  //输出结果为1
  return 0 ; 
}
  • 空的 class 同样也占用 1 个字节。

  • 计算类对象的大小时,类成员函数不占用对象空间,只需要考虑类中数据成员的大小。

class A 
{ 
  public: 
  static int a; 
  int b; 
  float c; 
  char d; 
};

int main() 
{ 
  A object; 
  cout << “sizeof(object) is ” << sizeof(object) << endl; 
  //输出结果为12
  return 0 ; 
}

因为在程序编译期间,就已经为 static 变量在静态存储区域分配了内存空间,并且这块内存在程序的整个运行期间都存在。而每次声明了类 A 的一个对象的时候,为该对象在堆上,根据对象的大小分配内存。

class A 
{ 
  public: 
  static int a; 
  int b; 
  float c; 
  char d; 
  int add(int x,int y) 
  { 
    return x+y; 
  } 
};

int main() 
{ 
  A object; 
  cout << “sizeof(object) is ” << sizeof(object) << endl; 
  b = object.add(3,4); 
  cout << “sizeof(object) is ” << sizeof(object) << endl; 
  //输出结果为12
  return 0 ; 
}

因为只有非静态类成员变量在新生成一个object的时候才需要自己的副本。所以每个非静态成员变量在生成新object需要内存,而function是不需要的。

3  与  区别

  •  是一个操作符, 是库函数。

  •  的参数可以是数据的类型,也可以是变量,而  只能以结尾

  • 编译器在编译时就计算出了  的结果,而  函数必须在运行时才能计算出来。并且  计算的是数据类型占内存的大小,而  计算的是字符串实际的长度。

  • 数组做  的参数不退化,传递给  就退化为指针了。如:

int ss[20]="0123456789";
 sizeof(ss)=80, //ss表示在内存中的大小,20*4。
 strlen(ss)    //错误,strlen的参数只能是char*,且必须是以“\0”结尾的。
 char *ss="0123456789";
 sizeof(ss)=4,  //ss是指向字符串常量的字符指针。
 sizeof(*ss)=1, // *ss是第一个字符。

参考资料