全面总结sizeof(字节对齐、数组、类(继承、虚函数)、结构体、strlen)*最全面*

本文详细总结了C++中sizeof操作符的使用,涵盖基本数据类型、指针、数组、结构体、类、联合体的情况,并对比了sizeof与strlen的区别。通过实例探讨了字节对齐的原则和特殊情况,同时指出sizeof在函数参数传递时的注意事项,以及其在内存分配和类型扩展中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原地址:http://www.vckbase.com/index.php/wv/789点击打开链接

http://blog.youkuaiyun.com/freefalcon/article/details/54839

http://www.cnblogs.com/chengxin1982/archive/2009/01/13/1374575.html

http://blog.youkuaiyun.com/shimazhuge/article/details/8238038

结合了几篇博文,一起总结了sizeof的问题。

关于sizeof的问题,并且本人对这个问题也一直没有得到很好的解决,索性今天对它来个较为详细的总结,同时结合strlen进行比较,如果能对大家有点点帮助,这是我最大的欣慰了。


一、好首先看看sizeof和strlen在MSDN上的定义:



首先看一MSDN上如何对sizeof进行定义的:

01. sizeof Operator
02.  
03. sizeof expression
04.  
05. The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type
06. (including aggregate types). This keyword returns a value of type size_t.
07.  
08. The expression is either an identifier or a type-cast expression (a type specifier enclosed in
09. parentheses).
10.  
11. When applied to a structure type or variable, sizeof returns the actual size, which may include
12. padding bytes inserted for alignment. When applied to a statically dimensioned array, sizeof
13. returns the size of the entire array. The sizeof operator cannot return the size of dynamically
14. allocated arrays or external arrays.


然后再看一下对strlen是如何定义的:

01. strlen
02.  
03. Get the length of a string.
04.  
05. Routine Required Header:
06. strlen <string.h>
07.  
08. size_t strlenconst char *string );
09. Parameter
10. string:Null-terminated string
11. Libraries
12. All versions of the C run-time libraries.
13.  
14. Return Value
15. Each of these functions returns the number of characters in string, excluding the terminal
16. NULL. No return value is reserved to indicate an error.
17.  
18. Remarks
19. Each of these functions returns the number of characters in string, not including the
20. terminating null character. wcslen is a wide-character version of strlen; the argument of
21. wcslen is a wide-character string. wcslen and strlen behave identically otherwise.

二、各类型的sizeof。


第一个例子: 基本数据类型的sizeof

这里的基本数据类型指short、int、long、float、double这样的简单内置数据类型,由于它们都是和系统相关的,所以在不同的系统下取值可能不同,这务必引起我们的注意,尽量不要在这方面给自己程序的移植造成麻烦。

一般的,在32位编译环境中,

sizeof int; //4byte
sizeof short; //2
sizeof long; //4
sizeof float; //4
sizeof double; //8
sizeof char; //1
sizeof p; //4
sizeof WORD; //2
sizeof DWORD; //4

一般的,在64位编译环境中,int为8byte ,指针为8byte 。

第二个例子: 指针变量的sizeof

学过数据结构的你应该知道指针是一个很重要的概念,它记录了另一个对象的地址。既然是来存放地址的,那么它当然等于计算机内部地址总线的宽度。所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位),可以预计,在将来的64位系统中指针变量的sizeof结果为8。

char* pc = "abc";
int* pi;
string* ps;
char** ppc = &pc;<pre name="code" class="java">char* pc = "abc";
int* pi;
string* ps;
char** ppc = &pc;
void (*pf)(); // 函数指针
sizeof( pc ); // 结果为4
sizeof( pi ); // 结果为4
sizeof( ps ); // 结果为4
sizeof( ppc ); // 结果为4
sizeof( pf ); // 结果为4


 
  

指针变量的sizeof值与指针所指的对象没有任何关系,正是由于所有的指针变量所占内存大小相等,所以MFC消息处理函数使用两个参数WPARAM、LPARAM就能传递各种复杂的消息结构(使用指向结构体的指针)。

第三个例子: 数组的sizeof

数组的sizeof值等于数组所占用的内存字节数,c风格字符串末尾还存在一个NULL(\0)终止符

01. char* ss = "0123456789";
02. sizeof(ss) 结果 4 ===》ss是指向字符串常量的字符指针
03. sizeof(*ss) 结果 1 ===》*ss是第一个字符
04.  
05. char ss[] = "0123456789";
06. sizeof(ss) 结果 11 ===》ss是数组,计算到\0位置,因此是10+1
07. sizeof(*ss) 结果 1 ===》*ss是第一个字符
08.  
09. char ss[100] = "0123456789";
10. sizeof(ss) 结果是100 ===》ss表示在内存中的大小 100×1
11. strlen(ss) 结果是10 ===》strlen是个函数内部实现是用一个循环计算到\0为止之前
12.  
13. int ss[100] = "0123456789";
14. sizeof(ss) 结果 400 ===》ss表示再内存中的大小 100×4
15. strlen(ss) 错误 ===》strlen的参数只能是char* 且必须是以''\0''结尾的
16.  
17. char q[]="abc";
18. char p[]="a\n";
19. sizeof(q),sizeof(p),strlen(q),strlen(p);
20. 结果是 4 3 3 2

一些朋友刚开始时把sizeof当作了求数组元素的个数,现在,你应该知道这是不对的,那么应该怎么求数组元素的个数呢?Easy,通常有下面两种写法:

char a1[] = "abc";
int c1 = sizeof( a1 ) / sizeof( char ); // 总长度/单个元素的长度
int c2 = sizeof( a1 ) / sizeof( a1[0] ); // 总长度/第一个元素的长度
void foo3(char a3[3])
{
    int c3 = sizeof( a3 ); // c3 == 4
}
void foo4(char a4[])
{
    int c4 = sizeof( a4 ); // c4 == 4
}
//也许当你试图回答c4的值时已经意识到c3答错了,是的,c3!=3。这里函数参数a3已不再是数组类型,而是蜕变成指针,相当于char* a3,为什么?仔细想想就不难明白,我们调用函数foo1时,程序会在栈上分配一个大小为3的数组吗?不会!数组是“传址”的,调用者只需将实参的地址传递过去,所以a3自然为指针类型(char*),c3的值也就为4。

char szPath[MAX_PATH]

如果在函数内这样定义,那么sizeof(szPath)将会是MAX_PATH,但是将szPath作为虚参声明时(void fun(char szPath[MAX_PATH])),sizeof(szPath)却会是4(指针大小)


第四个例子:结构体的sizeof

1为什么需要字节对齐?

计算机组成原理教导我们这样有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上,以此类推。这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。

2字节对齐的细节和编译器实现相关,但一般而言,满足三个准则

1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding)
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。
struct s1
{
char a;
double b;
int c;
char d; 
};

struct s2
{
char a;
char b;
int c;
double d;
};
cout<<sizeof(s1)<<endl; // 24
cout<<sizeof(s2)<<endl; // 16

    //同样是两个char类型,一个int类型,一个double类型,但是因为对齐问题,导致他们的大小不同。计算结构体大小可以采用元素摆放法,我举例子说明一下:首先,CPU判断结构体的对界,根据上面的结论,s1和s2的对界都取最大的元素类型,也就是double类型的对界8。然后开始摆放每个元素(满足准则1)。
  //对于s1,首先把a放到8的对界,假定是0,此时下一个空闲的地址是1,但是下一个元素d是double类型,要放到8的对界上,离1最接近的地址是8了,所以d被放在了8(满足准则2),此时下一个空闲地址变成了16,下一个元素c的对界是4,16可以满足,所以c放在了16,此时下一个空闲地址变成了20,下一个元素d需要对界1,也正好落在对界上,所以d放在了20,结构体在地址21处结束。由于s1的大小需要是8的倍数,所以21-23的空间被保留,s1的大小变成了24(满足准则3)。
  //对于s2,首先把a放到8的对界,假定是0,此时下一个空闲地址是1,下一个元素的对界也是1,所以b摆放在1,下一个空闲地址变成了2;下一个元素c的对界是4,所以取离2最近的地址4摆放c,下一个空闲地址变成了8,下一个元素d的对界是8,所以d摆放在8,所有元素摆放完毕,结构体在15处结束,占用总空间为16,正好是8的倍数。
通过上面的叙述,我们可以得到一个公式:
结构体的大小等于最后一个成员的偏移量加上其大小再加上末尾的填充字节数目,即:
sizeof( struct ) = offsetof( last item ) + sizeof( last item ) + sizeof( trailing padding )

3对于上面的准则,有几点需要说明:

1) 前面不是说结构体成员的地址是其大小的整数倍,怎么又说到偏移量了呢?因为有了第1点存在,所以我们就可以只考虑成员的偏移量,这样思考起来简单。想想为什么。
结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这个宏也在stddef.h中定义,如下:

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

例如,想要获得S2中c的偏移量,方法为

size_t pos = offsetof(S2, c);  // pos等于4

2) 基本类型是指前面提到的像char、short、int、float、double这样的内置数据类型,这里所说的“数据宽度”就是指其sizeof的大小。由于结构体的成员可以是复合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整体。但在确定复合类型成员的偏移位置时则是将复合类型的最宽基本类型成员的大小所整除存储时将复合类型作为整体看待(例如这个复合类型是结构体时,要满足之前的准则1):结构体变量的首地址能够被其最宽基本类型成员的大小所整除;)。

看下面的例子:
struct s1
{
char a[8];
};

struct s2
{
double d;
};

struct s3
{
s1 s;
char a;
};

struct s4
{
s2 s;
char a; 
};
cout<<sizeof(s1)<<endl; // 8
cout<<sizeof(s2)<<endl; // 8
cout<<sizeof(s3)<<endl; // 9
cout<<sizeof(s4)<<endl; // 16;


  s1 s2大小虽然都是8,但是s1的对齐方式是1s28double),所以在s3s4中才有这样的差异。
  
所以,在自己定义结构体的时候,如果空间紧张的话,最好考虑对齐因素来排列结构体里的元素。
struct S1
{
    char c;
    int i;
};
struct S2
{
    char c1;
    S1 s;
};
struct S3
{
    char c1;
    S1 s;
    char c2;
};

int main()
{
	
	cout << sizeof(S1) << endl;//8
	cout << sizeof(S2) << endl;//12
	cout << sizeof(S3) << endl;//16
	return 0;
}

  //对于s1,首先把c放到1的对界,假定是0,此时下一个空闲的地址是1,但是下一个元素i是int类型,要放到4的对界上,离1最接近的地址是4了,所以i被放在了4(满足准则2),结构体在地址8处结束。由于s1的大小需要是4的倍数,占用总空间为8,正好是8的倍数。(满足准则3)。
  //对于s2,首先把c1放到1的对界,假定是0,此时下一个空闲的地址是1,下一个元素的对界是s1最宽基本类型成员int的大小为4,所以S1的对象s摆放在4,s的大小为8,所以下一个空闲地址变成了12;结构体在地址12处结束。由于s2的大小需要是4(S2中最宽基本类型成员)的倍数,占用总空间为12,正好是4的倍数。(满足准则3)。
  //对于s3,首先把c1放到1的对界,假定是0,此时下一个空闲的地址是1,下一个元素的对界是s1最宽基本类型成员int的大小为4,所以S1的对象s摆放在4,s的大小为8,所以下一个空闲地址变成了12,把c2放到1的对界,是12,此时下一个空闲的地址是13,;结构体在地址13处结束。由于s3的大小需要是4(S3中最宽基本类型成员)的倍数,补齐到16。(满足准则3)。


3) 有一个影响sizeof的重要参量还未被提及,那便是编译器的pack指令。它是用来调整结构体对齐方式的,不同编译器名称和用法略有不同,VC6中通过#pragma pack实现,也可以直接修改/Zp编译开关。#pragma pack的基本用法为:#pragma pack( n ),n为字节对齐数,其取值为1、2、4、8、16,默认是8,如果这个值比结构体成员的sizeof值小,那么该成员的偏移量应该以此值为准,即是说,结构体成员的偏移量应该取二者的最小值,公式如下:offsetof( item ) = min( n, sizeof( item ) )
#pragma pack(push) // 将当前pack设置压栈保存
#pragma pack(2) // 必须在结构体定义之前使用
struct S1
{
    char c;
    int i;
};
struct S3
{
    char c1;
    S1 s;
    char c2;
};
#pragma pack(pop) // 恢复先前的pack设置

 计算sizeof(S1)时,min(2, sizeof(i))的值为2,所以i的偏移量为2,加上sizeof(i)等于6,能够被2整除,所以整个S1的大小为6。

同样,对于sizeof(S3),s的偏移量为2,c2的偏移量为8,加上sizeof(c2)等于9,不能被2整除,添加一个填充字节,所以sizeof(S3)等于10。


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

struct S5 { };
sizeof( S5 ); // 结果为1

第五个例子:含位域结构体的sizeof

位域成员不能单独被取sizeof值,我们这里要讨论的是含有位域的结构体的sizeof,只是考虑到其特殊性而将其专门列了出来。

C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。

使用位域的主要目的是压缩存储,其大致规则为:

1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。

示例1:

struct BF1
{
    char f1 : 3;
    char f2 : 4;
    char f3 : 5;
};

其内存布局为:

| f1  |  f2   | |  f3     |     |
---------------------------------
|  | | |  | | |  | | | | | |    | | | |
---------------------------------
0     3     7 8         13    16 (byte)

位域类型为char,第1个字节仅能容纳下f1和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(BF1)的结果为2。

示例2:
struct BF2
{
    char f1 : 3;
    short f2 : 4;
    char f3 : 5;
};
| f1  |  f2   | |  f3     |     |
---------------------------------
|  | | |  | | |  | | | | | |    | | | |
---------------------------------
0     3     7 8         13    16 (bit) 压缩
f1                                  f2             
不压缩时,首先把 f1放到1的对界,假定是0,此时下一个空闲的地址是1,但是下一个元素 f2是short类型,要放到2的对界上,离1最接近的地址是2了,所以 f2被放在了2(满足准则2),此时下一个空闲地址变成了4,下一个元素   f3的对界是1,4可以满足,所以   f3放在了4,此时下一个空闲地址变成了5,结构体在地址5处结束。由于BF2的大小需要是2的倍数,所以5的空间被保留,BF2的大小变成了6(满足准则3)。
由于相邻位域类型不同,在VC6中其sizeof为6,在Dev-C++中为2。
示例3:
struct BF3
{
    char f1 : 3;
    char f2;
    char f3 : 5;
};

非位域字段穿插在其中,不会产生压缩,在VC6和Dev-C++中得到的大小均为3。


补充:不要让double干扰你的位域 
  在结构体和类中,可以使用位域来规定某个成员所能占用的空间,所以使用位域能在一定程度上节省结构体占用的空间。不过考虑下面的代码: 

struct s1 
{ 
 int i: 8; 
 int j: 4; 
 double b; 
 int a:3; 
}; 

struct s2 
{ 
 int i; 
 int j; 
 double b; 
 int a; 
}; 

struct s3 
{ 
 int i; 
 int j; 
 int a; 
 double b; 
}; 

struct s4 
{ 
 int i: 8; 
 int j: 4; 
 int a:3; 
 double b; 
}; 

cout<<sizeof(s1)<<endl; // 24 
cout<<sizeof(s2)<<endl; // 24 
cout<<sizeof(s3)<<endl; // 24 
cout<<sizeof(s4)<<endl; // 16 


  可以看到,有double存在会干涉到位域(sizeof的算法参考上一节),所以使用位域的的时候,最好把float类型和double类型放在程序的开始或者最后。


第六个例子:类的sizeof(类似结构体)

01. class X
02. {
03. int i;
04. int j;
05. char k;
06. };
07. X x;
08. cout<<sizeof(X)<<endl; 结果 12 ===》内存补齐
09. cout<<sizeof(x)<<endl; 结果 12 同上

//空类的sizeof,有一个虚函数的类的sizeof
class A{};
class B
{
public:
	B() {}
	~B() {}
};
class C
{
public:
	C() {}
    virtual ~C() {}
};
int _tmain(int argc, _TCHAR* argv[])
{
	printf("%d, %d, %d\n", sizeof(A), sizeof(B), sizeof(C));//结果是 1 1 4
    return 0;
}

    //class A是一个空类型,它的实例不包含任何信息,本来求sizeof应该是0。但当我们声明该类型的实例的时候,它必须在内存中占有一定的空间,否则无法使用这些实例。至于占用多少内存,由编译器决定。Visual Studio 2008中每个空类型的实例占用一个byte的空间。 
   //class B在class A的基础上添加了构造函数和析构函数。由于构造函数和析构函数的调用与类型的实例无关(调用它们只需要知道函数地址即可),在它的实例中不需要增加任何信息。所以sizeof(B)和sizeof(A)一样,在Visual Studio 2008中都是1
   // class C在class B的基础上把析构函数标注为虚拟函数。C++的编译器一旦发现一个类型中有虚拟函数,就会为该类型生成虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针。在32位的机器上,一个指针占4个字节的空间,因此sizeof(C)是4。

#include "stdafx.h"
#include <iostream>
using namespace std;  
//编译器为每个有虚函数的类都建立一个虚函数表(其大小不计算在类中),并为这个类安插一个指向虚函数表的指针,即每个有虚函数的类其大小至少为一个指针的大小4   
class A
{   
public:   
	int a;   
	void Function();   
};   
class B
{   
public:   
	int a;   
	virtual void Function();   
};   
class C : public B
{   
public:   
	char b;   
};   
class D : public B
{   
public:   
	virtual void Function2();   
};   
class E
{   
public:   
	static void Function();   
};   
class staticE   
{   
	static int intVar;   
	static void fun(){} 
};  
void test1()
{
	cout<<"sizeof(A)="<<sizeof(A)<<endl;//4   (内含一个int,普通函数不占大小)   
	cout<<"sizeof(B)="<<sizeof(B)<<endl;//8   (一个int ,一个虚函数表指针)   
	cout<<"sizeof(C)="<<sizeof(C)<<endl;//12   (一个int ,一个虚函数表指针,一个char ,再加上数据对齐)  
	cout<<"sizeof(D)="<<sizeof(D)<<endl;//8 (一个int ,一个虚函数表指针,多个虚函数是放在一个表里的,所以虚函数表指针只要一个就行了)   
	cout<<"sizeof(E)="<<sizeof(E)<<endl;//1   (static 函数不占大小,空类大小为1)   
	cout<<"sizeof(staticE)="<<sizeof(staticE)<<endl;//1   静态数据成员不计入类内   
}
class cA   
{   
public:   
	virtual void PrintA1(void){}    
	virtual void PrintA2(void){}   
};   
class cB   
{   
public:   
	virtual void PrintB(void){}    
};   
class cC   
{   
public:   
	virtual void PrintC(void){}    
};   
class cD : public cA, public cB, public cC 
{   
};   
class cE : public cA, public cB, public cC   
{   
public:   
	virtual void PrintE(void){}    
};   
void test2()
{
	cout<<"sizeof(cD)="<<sizeof(cD)<<endl;//12 如果一个类里面什么也不实现,只实现一个或多个虚函数的话,测它的sizeof会得到4,但如果一个类从多个类继承,并且它的多个基类有虚函数的话,它就会有多个虚函数表了,这个在COM也有体现.
	cout<<"sizeof(cE)="<<sizeof(cE)<<endl;//12    
}
class dA 
{
	int a;
	virtual ~dA(){}
};
class dB:virtual public dA
{
	virtual void myfunB(){}
};
class dC:virtual public dA
{
	virtual void myfunC(){}
};
class dD:public dB,public dC
{
	virtual void myfunD(){}
};
void test3()
{ 
	cout<<"sizeof(dA)="<<sizeof(dA)<<endl;//8  
	cout<<"sizeof(dB)="<<sizeof(dB)<<endl;//12 
	cout<<"sizeof(dC)="<<sizeof(dC)<<endl;//12   
	cout<<"sizeof(dD)="<<sizeof(dD)<<endl;//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的大小和虚继承一样,但是内存布局却不同。
}
class T1   
{    
};   
class T2 : public T1   
{    
};   
class T3 : public T1   
{    
	int a;
};   

class M1   
{     
	virtual void GetM()=0;
};   
class M2 
{    
	virtual void GetM2()=0;
	virtual void Get();
};   
class M3 : public M1, public M2 
{    
	int a;
};   
void test4()
{
	cout<<"sizeof(T2)="<<sizeof(T2)<<endl;//1   继承一个空父类,这时sizeof(父类)=0
	cout<<"sizeof(T3)="<<sizeof(T3)<<endl;//4   同上
	cout<<"sizeof(M1)="<<sizeof(M1)<<endl;// 4
	cout<<"sizeof(M2)="<<sizeof(M2)<<endl;// 4
	cout<<"sizeof(M3)="<<sizeof(M3)<<endl;// 12  抽象函数与虚函数一样
}
 
void main()
{    
	test1();
	test2();
	test3();
	test4();
} 
/*虚函数表(http://www.uml.org.cn/c%2B%2B/200811143.asp) 
C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。
对C++了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了 这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
debug时,在局部变量窗口中可以看到,虚函数表
*/


 

第七个例子:联合体的sizeof

结构体在内存组织上是顺序式的,联合体则是重叠式,各成员共享一段内存,所以整个联合体的sizeof也就是每个成员sizeof的最大值。结构体的成员也可以是复合类型,这里,复合类型成员是被作为整体考虑的。

所以,下面例子中,U的sizeof值等于sizeof(s)。

union U
{
    int i;
    char c;
    S1 s;
};


三、sizeof深入理解。

1.sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。

2.sizeof也可以对一个函数调用求值,其结果是函数返回类型的大小,函数并不会被调用,我们来看一个完整的例子:

char foo()
{   printf("foo() has been called./n");
    return 'a';
}
int main()
{
   size_t sz = sizeof( foo() ); // foo() 的返回值类型为char,所以sz = sizeof( char ),foo()并不会被调用
    printf("sizeof( foo() ) = %d/n", sz); 
}

sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。sizeof还可以用函数做参数,比如:

1. short f();
2. printf("%d\n"sizeof(f()));

输出的结果是sizeof(short),即2。

3. sizeof的常量性

sizeof的计算发生在编译时刻,所以它可以被当作常量表达式使用,如:

char ary[ sizeof( int ) * 10 ]; // ok

最新的C99标准规定sizeof也可以在运行时刻进行计算,如下面的程序在Dev-C++中可以正确执行:

int n;
n = 10; // n动态赋值
char ary[n]; // C99也支持数组的动态定义
printf("%d/n", sizeof(ary)); // ok. 输出10


但在没有完全实现C99标准的编译器中就行不通了,上面的代码在VC6中就通不过编译。所以我们最好还是认为sizeof是在编译期执行的,这样不会带来错误,让程序的可移植性强些。

4.数组做sizeof的参数不退化,传递给strlen就退化为指针了。

char ss[100] = "0123456789";
10. sizeof(ss) 结果是100 ===》ss表示在内存中的大小 100×1
11. strlen(ss) 结果是10 ===》strlen是个函数内部实现是用一个循环计算到\0为止之前

5.大部分编译程序 在编译的时候就把sizeof计算过了 是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因

1. char str[20]="0123456789";
2. int a=strlen(str); //a=10;
3. int b=sizeof(str); //而b=20;

6.strlen的结果要在运行的时候才能计算出来,时用来计算字符串的长度,不是类型占内存的大小。

7.sizeof是算符,strlen是函数。sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。

sizeof有三种语法形式,如下:

1) sizeof( object ); // sizeof( 对象 );
2) sizeof( type_name ); // sizeof( 类型 );
3) sizeof object; // sizeof 对象;

所以,

int i;
sizeof( i ); // ok
sizeof i; // ok
sizeof( int ); // ok
sizeof int; // error

既然写法3可以用写法1代替,为求形式统一以及减少我们大脑的负担,第3种写法,忘掉它吧!

实际上,sizeof计算对象的大小也是转换成对对象类型的计算,也就是说,同种类型的不同对象其sizeof值都是一致的。这里,对象可以进一步延伸至表达式,即sizeof可以对一个表达式求值,编译器根据表达式的最终结果类型来确定大小,一般不会对表达式进行计算。如:

sizeof( 2 ); // 2的类型为int,所以等价于 sizeof( int );
sizeof( 2 + 3.14 ); // 3.14的类型为double,2也会被提升成double类型,所以等价于 sizeof( double );

8.当适用了于一个结构类型时或变量, sizeof 返回实际的大小; 当适用一静态地空间数组, sizeof 归还全部数组的尺 寸。 sizeof 操作符不能返回动态地被分派了的数组或外部的数组的尺寸

9.数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,如:

1. fun(char [8])
2. fun(char [])

都等价于 fun(char *) 在C++里传递数组永远都是传递指向数组首元素的指针,编译器不知道数组的大小如果想在函数内知道数组的大小, 需要这样做:进入函数后用memcpy拷贝出来,长度由另一个形参传进去

1. fun(unsiged char *p1, int len)
2. {
3. unsigned char* buf = new unsigned char[len+1]
4. memcpy(buf, p1, len);
5. }

有关内容见: C++ PRIMER?

10.计算结构变量的大小就必须讨论数据对齐问题。为了CPU存取的速度最快(这同CPU取数操作有关,详细的介绍可以参考一些计算机原理方面的书),C++在处理数据时经常把结构变量中的成员的大小按照4或8的倍数计算,这就叫数据对齐(data alignment)。这样做可能会浪费一些内存,但理论上速度快了。当然这样的设置会在读写一些别的应用程序生成的数据文件或交换数据时带来不便。MS VC++中的对齐设定,有时候sizeof得到的与实际不等。一般在VC++中加上#pragma pack(n)的设定即可.或者如果要按字节存储,而不进行数据对齐,可以在Options对话框中修改Advanced compiler页中的Data alignment为按字节对齐。

11.sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。如sizeof(max)若此时变量max定义为int max(),sizeof(char_v) 若此时char_v定义为char char_v [MAX]且MAX未知,sizeof(void)都不是正确形式

C99标准规定,函数、不能确定类型的表达式以及位域(bit-field)成员不能被计算sizeof值,即下面这些写法都是错误的:


#include<iostream>
using namespace std;

sizeof( foo ); // error
void foo2() { }
sizeof( foo2() ); // error


struct S
{
    unsigned int f1:3 ;
    unsigned int f2 ;
    unsigned int f3 ;
};

int main()
{
	S s;
	cout <<sizeof( S.f2 ); // error
	cout <<sizeof( s.f1 ); // error
	cout <<sizeof( s.f2 ); //right
	return 0;
}


四、结束语

sizeof使用场合。

1.sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。例如: 

1. void *mallocsize_t size), 
2. size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream)。

2.用它可以看看一类型的对象在内存中所占的单元字节。

1. void * memsetvoid * s,int c,sizeof(s))

3.在动态分配一对象时,可以让系统知道要分配多少内存。

4.便于一些类型的扩充,在windows中就有很多结构内型就有一个专用的字段是用来放该类型的字节大小。

5.由于操作数的字节数在实现时可能出现变化,建议在涉及到操作数字节大小时用sizeof来代替常量计算。

6.如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值