??xml version="1.0" encoding="utf-8" standalone="yes"?>
int a=256;
printf("%d\n", sizeof(++a));
printf("%d\n", a);
那么到底打印的是多少呢?
应该??56Q我想第一个答案大家应该已l没有问题了Q但是ؓ(f)什么在++a以后Qa的数D是没有发生变化呢Q因为sizeofQ)是一个运符Q在其中的所有的q算都是无效的,所?+aҎ(gu)没有运行?/p>
上面的一个例子提醒我们,虽然sizeof看这单,但是其中q是有很多的问题值得讨论的,呵呵?br>
一、sizeof的概?/strong>
sizeof是C语言的一U单目操作符Q如C语言的其他操作符++?-{。它q不是函数。sizeof操作W以字节形式l出了其操作数的存储大小?span class="Title">操作数可以是一个表辑ּ或括在括号内的类型名。操作数的存储大由操作数的cd军_。
二、sizeof的用方法
1、用于数据类型
sizeof使用形式QsizeofQtypeQ
数据cd必须用括h住。如sizeofQintQ。
2、用于变量
sizeof使用形式QsizeofQvar_nameQ或sizeof var_name
变量名可以不用括h住。如sizeof (var_name)Qsizeof var_name{都是正Ş式。带括号的用法更普遍Q大多数E序员采用这UŞ式。
注意Q?/strong>sizeof操作W不能用于函数类型,不完全类型或位字Dc不完全cd指具有未知存储大的数据cdQ如未知存储大小的数l类型、未知内容的l构或联合类型、voidcd{。
如sizeof(max)若此时变量max定义为int max(),sizeof(char_v) 若此时char_v定义为char char_v [MAX]且MAX未知Qsizeof(void)都不是正Ş式。
三、sizeof的结果
sizeof操作W的l果cd是size_tQ它在头文g
中typedef为unsigned intcd。该cd保证能容U_现所建立的最大对象的字节大小。
1、若操作数具有类型char、unsigned char或signed charQ其l果{于1。
ANSI C正式规定字符cd?字节。
2、int、unsigned int 、short int、unsigned short 、long int 、unsigned long ?
float、double、long doublecd的sizeof 在ANSI C中没有具体规定,大小依赖于实玎ͼ一般可能分别ؓ(f)2???
2?nbsp;4????0。
3、当操作数是指针Ӟsizeof依赖于编译器。例如Microsoft C/C++7.0中,nearcL针字节数?Qfar、hugecL针字节数?。一般Unix的指针字节数?。
4、当操作数具有数l类型时Q其l果是数l的d节数。
5、联合类型操作数的sizeof是其最大字节成员的字节数。结构类型操作数的sizeof是这U类型对象的d节数Q包括Q何垫补在内。
让我们看如下l构Q
struct {char b; double x;} a;
在某些机器上sizeofQaQ?12Q而一般sizeofQcharQ? sizeofQdoubleQ?9。
q是因ؓ(f)~译器在考虑寚w问题Ӟ在结构中插入IZ以控制各成员对象的地址寚w。如doublecd的结构成员x要放在被4整除的地址。
6、如果操作数是函C的数lŞ参或函数cd的Ş参,sizeofl出其指针的大小。
四、sizeof与其他操作符的关pR
sizeof的优先?U,??{?U运符优先U高。它可以与其他操作符一L(fng)成表辑ּ。如i*sizeofQintQ;其中i为intcd变量。
五、sizeof的主要用途
1、sizeof操作W的一个主要用途是与存储分配和I/Opȝ那样的例E进行通信。例如:(x)
void *mallocQsize_t sizeQ?
size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream)。
2、sizeof的另一个的主要用途是计算数组中元素的个数。例如:(x)
void * memsetQvoid * s,int c,sizeof(s)Q。
六、徏议
׃操作数的字节数在实现时可能出现变化,在涉?qing)到操作数字节大时用sizeof来代替常量计?br>
=============================================================
本文主要包括二个部分Q第一部分重点介绍在VC中,怎么样采用sizeof来求l构的大,以及(qing)Ҏ(gu)出现的问题,q给决问题的Ҏ(gu)Q第二部分ȝ出VC中sizeof的主要用法?nbsp;
1?nbsp;sizeof应用在结构上的情?/strong>
L(fng)下面的结构:(x)
struct MyStruct
{
double dda1;
char dda;
int type
};
对结构MyStruct采用sizeof?x)出C么结果呢Qsizeof(MyStruct)为多呢Q也怽?x)这hQ?nbsp;
sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13
但是当在VC中测试上面结构的大小Ӟ你会(x)发现sizeof(MyStruct)?6。你知道Z么在VC中会(x)得出q样一个结果吗Q?nbsp;
?
实,q是VC对变量存储的一个特D处理。ؓ(f)了提高CPU的存储速度QVC对一些变量的起始地址做了"寚w"处理。在默认情况下,VC规定各成员变量存攄
起始地址相对于结构的起始地址的偏U量必须变量的类型所占用的字节数的倍数。下面列出常用类型的寚w方式(vc6.0,32位系l??nbsp;
cd
寚w方式Q变量存攄起始地址相对于结构的起始地址的偏U量Q?nbsp;
Char
偏移量必Mؓ(f)sizeof(char)?的倍数
int
偏移量必Mؓ(f)sizeof(int)?的倍数
float
偏移量必Mؓ(f)sizeof(float)?的倍数
double
偏移量必Mؓ(f)sizeof(double)?的倍数
Short
偏移量必Mؓ(f)sizeof(short)?的倍数
?
成员变量在存攄时候根据在l构中出现的序依次甌I间Q同时按照上面的寚w方式调整位置Q空~的字节VC?x)自动填充。同时VCZ保l构的大ؓ(f)l?
构的字节边界敎ͼ卌l构中占用最大空间的cd所占用的字节数Q的倍数Q所以在为最后一个成员变量申L(fng)间后Q还?x)根据需要自动填充空~的字节?nbsp;
下面用前面的例子来说明VC到底怎么h存放l构的?nbsp;
struct MyStruct
{
double dda1;
char dda;
int type
}Q?nbsp;
?
上面的结构分配空间的时候,VCҎ(gu)成员变量出现的顺序和寚w方式Q先为第一个成员dda1分配I间Q其起始地址跟结构的起始地址相同Q刚好偏U量0刚好
为sizeof(double)的倍数Q,该成员变量占用sizeof(double)=8个字节;接下来ؓ(f)W二个成员dda分配I间Q这时下一个可以分
配的地址对于l构的v始地址的偏U量?Q是sizeof(char)的倍数Q所以把dda存放在偏U量?的地Ҏ(gu)_齐方式,该成员变量占?
sizeof(char)=1个字节;接下来ؓ(f)W三个成员type分配I间Q这时下一个可以分配的地址对于l构的v始地址的偏U量?Q不?
sizeof (int)=4的倍数Qؓ(f)了满_齐方式对偏移量的U束问题QVC自动填充3个字节(q三个字节没有放什么东西)Q这时下一个可以分配的?
址对于l构的v始地址的偏U量?2Q刚好是sizeof(int)=4的倍数Q所以把type存放在偏U量?2的地方,该成员变量占用sizeof
(int)=4个字节;q时整个l构的成员变量已l都分配了空_(d)ȝ占用的空间大ؓ(f)Q?+1+3+4=16Q刚好ؓ(f)l构的字节边界数Q即l构中占用最
大空间的cd所占用的字节数sizeof(double)=8Q的倍数Q所以没有空~的字节需要填充。所以整个结构的大小为:(x)sizeof
(MyStruct)=8+1+ 3+4=16Q其中有3个字节是VC自动填充的,没有放Q何有意义的东ѝ?nbsp;
下面再D个例子,交换一下上面的MyStruct的成员变量的位置Q它变成下面的情况Q?nbsp;
struct MyStruct
{
char dda;
double dda1;
int type
}Q?nbsp;
q个l构占用的空间ؓ(f)多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)?4。结合上面提到的分配I间的一些原则,分析下VC怎么样ؓ(f)上面的结构分配空间的。(单说明)
struct MyStruct
{
char dda;//偏移量ؓ(f)0Q满_齐方式,dda占用1个字节;
double dda1;//下一个可用的地址的偏U量?Q不是sizeof(double)=8
//的倍数Q需要补?个字节才能偏移量变?Q满_?nbsp;
//方式Q,因此VC自动填充7个字节,dda1存放在偏U量?
//的地址上,它占?个字节?nbsp;
int typeQ?/下一个可用的地址的偏U量?6Q是sizeof(int)=4的?nbsp;
//敎ͼ满int的对齐方式,所以不需要VC自动填充Qtype?nbsp;
//攑֜偏移量ؓ(f)16的地址上,它占?个字节?nbsp;
}Q?/所有成员变量都分配了空_(d)I间ȝ大小?+7+8+4=20Q不是结?nbsp;
//的节边界敎ͼ即结构中占用最大空间的cd所占用的字节数sizeof
//(double)=8Q的倍数Q所以需要填?个字节,以满结构的大小?nbsp;
//sizeof(double)=8的倍数?nbsp;
所以该l构ȝ大小为:(x)sizeof(MyStruc)?+7+8+4+4=24。其中ȝ?+4=11个字节是VC自动填充的,没有放Q何有意义的东ѝ?nbsp;
VC对结构的存储的特D处理确实提高CPU存储变量的速度Q但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以讑֮变量的对齐方式?nbsp;
#pragma pack(n)
VC
中提供了#pragma pack(n)来设定变量以n字节寚w方式。n字节寚w是说变量存攄起始地址的偏U量有两U情况:(x)W一、如果n大于{于该变
量所占用的字节数Q那么偏U量必须满默认的对齐方式,W二、如果n于该变量的cd所占用的字节数Q那么偏U量为n的倍数Q不用满默认的寚w方式。结
构的d也有个U束条gQ分下面两种情况Q如果n大于所有成员变量类型所占用的字节数Q那么结构的d必Mؓ(f)占用I间最大的变量占用的空间数的倍数Q?
否则必须为n的倍数。下面D例说明其用法?nbsp;
#pragma pack(push) //保存寚w状?nbsp;
#pragma pack(4)//讑֮?字节寚w
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢复寚w状?nbsp;
?
上结构的大小?6Q下面分析其存储情况Q首先ؓ(f)m1分配I间Q其偏移量ؓ(f)0Q满x们自p定的寚w方式Q?字节寚wQ,m1占用1个字节。接着开始ؓ(f)
m4分配I间Q这时其偏移量ؓ(f)1Q需要补?个字节,q样使偏U量满为n=4的倍数Q因为sizeof(double)大于nQ?m4占用8个字节?
接着为m3分配I间Q这时其偏移量ؓ(f)12Q满ؓ(f)4的倍数Qm3占用4个字节。这时已lؓ(f)所有成员变量分配了I间Q共分配?6个字节,满为n的倍数?
如果把上面的#pragma pack(4)改ؓ(f)#pragma pack(16)Q那么我们可以得到结构的大小?4。(误者自己分析)
2?nbsp;sizeof用法ȝ
在VC中,sizeof有着许多的用法,而且很容易引起一些错误。下面根据sizeof后面的参数对sizeof的用法做个ȝ?nbsp;
AQ?nbsp; 参数为数据类型或者ؓ(f)一般变量。例如sizeof(int),sizeof(long){等。这U情况要注意的是不同pȝpȝ或者不同编译器得到的结果可能是不同的。例如intcd?6位系l中?个字节,?2位系l中?个字节?nbsp;
BQ?nbsp; 参数为数l或指针。下面D例说?
int a[50]; //sizeof(a)=4*50=200; 求数l所占的I间大小
int *a=new int[50];// sizeof(a)=4; aZ个指针,sizeof(a)是求指针
//的大??2位系l中Q当然是?个字节?nbsp;
CQ?nbsp; 参数为结构或cRSizeof应用在类和结构的处理情况是相同的。但有两炚w要注意,W一、结构或者类中的静态成员不对结构或者类的大生媄响,因ؓ(f)静态变量的存储位置与结构或者类的实例地址无关?nbsp;
W二、没有成员变量的l构或类的大ؓ(f)1Q因为必M证结构或cȝ每一
个实例在内存中都有唯一的地址?nbsp;
下面举例说明Q?nbsp;
Class Test{int a;static double c};//sizeof(Test)=4.
Test *s;//sizeof(s)=4,sZ个指针?nbsp;
Class test1{ };//sizeof(test1)=1;
DQ?nbsp; 参数为其他。下面D例说明?nbsp;
int func(char s[5]);
{
cout<<sizeof(s);//q里输?Q本来sZ个数l,但由于做为函
//数的参数在传递的时候系l处理ؓ(f)一个指针,所
//以sizeof(s)实际上ؓ(f)求指针的大小?nbsp;
return 1;
}
sizeof(func("1234"))=4//因ؓ(f)func的返回类型ؓ(f)intQ所以相当于
//求sizeof(int).
以上为sizeof的基本用法,在实际的使用中要注意分析VC的分配变量的分配{略Q这L(fng)话可以避免一些错误?br>
打开Source Insight来阅读EduOS的源代码,我们在stdio.c里找Cprintf的实C?首先看看对printf的定?
[code]
int printf (const char *cntrl_string, ...)
[/code]
W一个参数cntrl_string是控制字W串,也就是^常我们写?d,%f的地?紧接着后面是一个变长参?
看看函数头部的定?
[code]int pos = 0, cnt_printed_chars = 0, i;
unsigned char* chptr;
va_list ap;[/code]
马上?除了ap我们可以马上判断出来是用来读取变长参数的,i用于循环变量.其他变量都不知道是怎么回事.不要着?我们边看代码边分?代码的第一行必然是
[code]va_start (ap, cntrl_string);[/code]
用来初始化变长参?
接下来是一个while循环
[code]while (cntrl_string[pos]) {
...
}[/code]
l束条g是cntrl_string[pos]为NULL,昄q个循环是用来遍历整个控制字W串?自然pos是当前遍历到的位置?q入循环首先闯入视线的是
[code] if (cntrl_string[pos] == '%') {
pos++;
...
} [/code]
开门见?上来当前字W是否办断是?.一猜就知道如果成立pos++马上取出下一个字W在d,f,l{等之间q行判断.往下一?果真不出所?
[code]switch (cntrl_string[pos]) {
case 'c':
...
case 's':
...
case 'i':
...
case 'd':
...
case 'u':
...[/code]
用上switch-case? 快速浏览一下下面的代码.
首先看看case 'c'的部?/p>
[code]case 'c':
putchar (va_arg (ap, unsigned char));
cnt_printed_chars++;
break;[/code]
%c表示仅仅输出一个字W?因此先通过va_argq行参数的类型{?之后用putchar[1]输出到屏q上?之后?br>cnt_printed_chars++,通过q句我们可以判断出cnt_printed_chars使用来表C?已经被printf输出的字W个数的.
再来看看 case 's':
[code] case 's':
chptr = va_arg (ap, unsigned char*);
i = 0;
while (chptr [i]) {
cnt_printed_chars++;
putchar (chptr [i++]);
}
break;[/code]和case 'c',同出一?cnt_printed_chars++攑֜了@环内,也证明了刚才提到的他的作?另外我们也看Ccnptr是用来在处理字符串时的位|指?到此为止,我们清楚的所有变量的用?前途变得更加光明了.
接下?
[code]// PartI
case 'i':
case 'd':
cnt_printed_chars += printInt (va_arg (ap, int));
break;
case 'u':
cnt_printed_chars += printUnsignedInt (va_arg (ap, unsigned int));
break;
case 'x':
cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'x');
break;
case 'X':
cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'X');
break;
case 'o':
cnt_printed_chars += printOctal (va_arg (ap, unsigned int));
break;
// Part II
case 'p':
putchar ('0');
putchar ('x');
cnt_printed_chars += 2; /* of '0x' */
cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'x');
break;
case '#':
pos++;
switch (cntrl_string[pos]) {
case 'x':
putchar ('0');
putchar ('x');
cnt_printed_chars += 2; /* of '0x' */
cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'x');
break;
case 'X':
putchar ('0');
putchar ('X');
cnt_printed_chars += 2; /* of '0X' */
cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'X');
break;
case 'o':
putchar ('0');
cnt_printed_chars++;
cnt_printed_chars += printOctal (va_arg (ap, unsigned int));
break;[/code]
注意观察一?PartII的代码其实就是比PartI的代码多一个样??6q制数或八进制前加入0x或是o,{等.因此q里只分析一下PartI?
其实仔细看看PartI的个条case,也就是把参数分发C更具体的函数用于昄,然后以返回值的形式q回输出个数.对于q些函数׃具体分析?我们先来看看一些善后处?
先看case的default处理.
[code]default:
putchar ((unsigned char) cntrl_string[pos]);
cnt_printed_chars++;[/code]是直接输出cntrl_string?号后面的未知字符.应该是一U容错设计处?
再看看if (cntrl_string[pos] == '%')的else部分
[code]else {
putchar ((unsigned char) cntrl_string[pos]);
cnt_printed_chars++;
pos++;
}[/code]
如果不是%开头的,那么直接输出q个字符.
最后函数返回前
[code]va_end (ap);
return cnt_printed_chars;[/code]va_end处理变长参数的善后工?q返回输出的字符个数.
在最后我们有必要谈谈putChar函数以及(qing)基本输出的基函数printChar,先来看看putChar
[code]int putchar (int c) {
switch ((unsigned char) c) {
case '\n' :
newLine ();
break;
case '\r' :
carriageReturn ();
break;
case '\f' :
clearScreen ();
break;
case '\t' :
printChar (32); printChar (32); /* 32 = space */
printChar (32); printChar (32);
printChar (32); printChar (32);
printChar (32); printChar (32);
break;
case '\b':
backspace ();
break;
case '\a':
beep ();
break;
default :
printChar ((unsigned char) c);
}
return c;
}[/code]
?
览一?也是switch-caseZ体的.主要是用来应对一些特D字W?如\n,\r,....q里需要提一?关于\t的理?有些为\t是
8个space,有些人则认ؓ(f),屏幕分ؓ(f)10大列(每个大列8个小列d80?.一个\tpC一个大列输?也就是说不管你现在实在屏q的W?
1,2,3,4,5,6,7位置输出字符,只要一个\t都在W?个位|开始输?
VS.NET中就是用的这U理?因此如果按照q个理解的话,\t的实现可以这?/p>
[code]int currentX = ((currentX % 10) + 1) * 8;[/code]
然后在currentX位置输出.
接下来看printChar也就是输出部分最低层的操作咯
[code]void printChar (const byte ch) {
*(word *)(VIDEO + y * 160 + x * 2) = ch | (fill_color << 8);
x++;
if (x >= WIDTH)
newLine ();
setVideoCursor (y, x);
}[/code]
q里VIDEO表示昑֭地址也就?xB8000.通过 y * 160 + x
屏幕(x,y)坐标在显存中的位|?q里需要知?一个字W显C需要两个字?一个是ASCII?W二个是字符属性代码也是颜色代码.因此才必?
y * 80 * 2 + x = y * 160 + x.那么ch | (fill_color <<
8)也自然就是写入字W及(qing)属性代码用的了.每写一个字W光标位|加1,如果大于屏幕宽度WIDTH换?最后通过setVideoCursor讄新的
光标位置.完成了整个printCharq程.