原地址: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
strlen
(
const
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
第二个例子: 指针变量的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(指针大小)
char
szPath[MAX_PATH]
如果在函数内这样定义,那么sizeof(szPath)将会是MAX_PATH,但是将szPath作为虚参声明时(void fun(char szPath[MAX_PATH])),sizeof(szPath)却会是4(指针大小)
第四个例子:结构体的sizeof
1为什么需要字节对齐?
2字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
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的倍数。
3对于上面的准则,有几点需要说明:
1) 前面不是说结构体成员的地址是其大小的整数倍,怎么又说到偏移量了呢?因为有了第1点存在,所以我们就可以只考虑成员的偏移量,这样思考起来简单。想想为什么。
结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这个宏也在stddef.h中定义,如下:
例如,想要获得S2中c的偏移量,方法为
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的对齐方式是1,s2是8(double),所以在s3和s4中才有这样的差异。
所以,在自己定义结构体的时候,如果空间紧张的话,最好考虑对齐因素来排列结构体里的元素。
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)。
#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可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。
使用位域的主要目的是压缩存储,其大致规则为:
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。
示例1:
struct BF1
{
char f1 : 3;
char f2 : 4;
char f3 : 5;
};
其内存布局为:
---------------------------------
| | | | | | | | | | | | | | | | |
---------------------------------
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) 压缩
由于相邻位域类型不同,在VC6中其sizeof为6,在Dev-C++中为2。
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有三种语法形式,如下:
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
*
malloc
(
size_t
size),
2.
size_t
fread
(
void
* ptr,
size_t
size,
size_t
nmemb,
FILE
* stream)。
2.用它可以看看一类型的对象在内存中所占的单元字节。
1.
void
*
memset
(
void
* s,
int
c,
sizeof
(s))
3.在动态分配一对象时,可以让系统知道要分配多少内存。
4.便于一些类型的扩充,在windows中就有很多结构内型就有一个专用的字段是用来放该类型的字节大小。
5.由于操作数的字节数在实现时可能出现变化,建议在涉及到操作数字节大小时用sizeof来代替常量计算。
6.如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。