第二節(jié) 數(shù)據(jù)存儲(chǔ)與變量
2.1 變量的聲明與定義
1. 如程序清單2. 1所示會(huì)不會(huì)報(bào)錯(cuò)?為什么?如果不會(huì)報(bào)錯(cuò),又是輸出什么結(jié)果?
程序清單2. 1 變量的聲明與定義
#include
static int a ;
static int b[] ;
int main( int argc , char *argv[] )
{
printf( "%d %d \n" , a , b[0] ) ;
return 0 ;
}
static int a = 8 ;
static int b[4] ;
這個(gè)程序是不會(huì)報(bào)錯(cuò)的,并且連警告都不會(huì)出現(xiàn)。輸出的結(jié)果是:8 0
static int a ,這句程序是聲明全局變量a;static int b[],這句程序是聲明全局?jǐn)?shù)組變量b,并且是不完全聲明,也就是可以省略數(shù)組下標(biāo)。static int a = 8,這里才是定義全局變量a,static int b[4],這里是定義全局變量b。
2.2 局部變量與全局變量的較量
1. 請(qǐng)問如程序清單2. 2所示輸出什么?
程序清單2. 2 局部變量與全局變量
#include
static int a = 8 ;
int main( int argc , char *argv[] )
{
int a = 4 ;
printf( "%d \n" , a ) ;
return 0 ;
}
C語言規(guī)定,局部變量在自己的可見范圍內(nèi)會(huì)“擋住”同名的全局變量,讓同名的全局變量臨時(shí)不可見。即在局部變量的可見范圍內(nèi)不能訪問同名的全局變量。因此本程序輸出為:4。
2.3 char、int、float、double的數(shù)據(jù)存儲(chǔ)
1. 請(qǐng)問如程序清單2. 3所示,i和j輸出什么?
程序清單2. 3 數(shù)據(jù)存儲(chǔ)
float i = 3 ;
int j = *(int*)(&i) ;
printf( "i = %f \n" , i ) ;
printf( "j = %#x \n" , j ) ;
i是毋庸置疑是:3.000000。但是j呢?3.000000?答案是否定的,j是輸出:0x4040 0000。有人會(huì)問了,難道j是隨機(jī)輸出?瞎說,j輸出0x4040 0000是有依據(jù),是一個(gè)定值!
由于i是float數(shù)據(jù)類型,而j是int數(shù)據(jù)類型。理論上說,j是取了i的地址然后再去地址,應(yīng)該得到的就是i的值:3。但是問題的關(guān)鍵就是float數(shù)據(jù)類型的存儲(chǔ)方式和int數(shù)據(jù)類型不一樣,float是占用4個(gè)字節(jié)(32位),但是float存儲(chǔ)是使用科學(xué)計(jì)數(shù)法存儲(chǔ),最高位是存儲(chǔ)數(shù)符(負(fù)數(shù)的數(shù)符是0,正數(shù)的數(shù)符是1);接下來8位是存儲(chǔ)階碼;剩下的23位是存儲(chǔ)尾數(shù)。上面i=3.000000,那么3.000000(10進(jìn)制) = 11(2進(jìn)制) = (二進(jìn)制)。數(shù)據(jù)在電腦中存儲(chǔ)都是二進(jìn)制,這個(gè)應(yīng)該都沒有疑問。那么這里的數(shù)符為:0 ,階碼為:E – 127 = 1 ,那么階碼為:E = 128 即為:1000 0000 (2進(jìn)制) ,尾數(shù)為:100 0000 0000 0000 0000 0000 。那么存儲(chǔ)形式就是:0100 0000 0100 0000 0000 0000 0000 0000。這個(gè)數(shù)據(jù)轉(zhuǎn)換成16進(jìn)制就是0x4040 0000。
圖2. 1 數(shù)據(jù)存儲(chǔ)方式
char、int、float、double的存儲(chǔ)方式如圖2. 1所示。
提問:如果i = -3.5 的話,請(qǐng)問j輸出多少?
i = -3.500000
j = 0xc0600000
這個(gè)希望讀者自行分析。
再問:如果如程序清單2. 4所示。
程序清單2. 4 數(shù)據(jù)存儲(chǔ)
double i = 3 ;
int j = *(int*)(&i) ;
printf( "i = %lf \n" , i ) ;
printf( "j = %#x \n" , j ) ;
這樣的話,j又輸出多少呢?
提示:double( 8個(gè)字節(jié)(64位) )的存儲(chǔ)方式是:最高位存儲(chǔ)數(shù)符,接下來11位存儲(chǔ)階碼,剩下52位存儲(chǔ)尾數(shù)。
是不是得不到你想要的結(jié)果?double是8個(gè)字節(jié),int是4個(gè)字節(jié)。一定別忘記了這個(gè)。用這個(gè)方法也同時(shí)可以驗(yàn)證大小端模式!
2.4 容易忽略char的范圍
1. 如程序清單2. 5所示,假設(shè)&b=0x12ff54,請(qǐng)問三個(gè)輸出分別為多少?
程序清單2. 5 char的范圍
unsigned int b = 0x12ff60 ;
printf("( (int)(&b)+1 ) = %#x \n" , ( (int)(&b)+1 ) ) ;
printf("*( (int*)( (int)(&b)+1 ) ) = %#x \n" , *( (int*)( (int)(&b)+1 ) ) ) ;
printf("*( (char*)( (int)(&b)+1 ) ) = %#x \n" , *( (char*)( (int)(&b)+1 ) ) ) ;
很顯然,&b是取無符號(hào)整型b變量的地址,那么(int)(&b)是強(qiáng)制轉(zhuǎn)換為整型變量,那么加1即為0x12ff54+1=0x12ff55。所以( (int)(&b)+1 )是0x12ff55。
圖2. 3 指針加1取字符型數(shù)據(jù)
由于( (int)(&b)+1 )是整型數(shù)據(jù)類型,通過(int *)( (int)(&b)+1 )轉(zhuǎn)化為了整型指針類型,說明要占4個(gè)字節(jié),即為:0x12ff55、0x12ff56、0x12ff57、0x12ff58,再去地址*( (int *)( (int) (&b)+1 ) )得到存儲(chǔ)在這4個(gè)字節(jié)中的數(shù)據(jù)。但是很遺憾,0x12ff58我們并不知道存儲(chǔ)的是什么,所以我們只能寫出0x**0012ff。**表示存儲(chǔ)在0x12ff58中的數(shù)據(jù)。如圖2. 2所示。
圖2. 2 指針加1取整型數(shù)據(jù)
以此類推,*( (char *)( (int) (&b)+1 ) ) = 0xff。如圖2. 3所示。
但是,*( (char *)( (int) (&b)+1 ) )輸出的卻是:0xff ff ff ff !
問題出現(xiàn)了,為什么*( (char *)( (int) (&b)+1 ) )不是0xff,而是0xff ff ff ff?char型數(shù)據(jù)應(yīng)該占用1個(gè)字節(jié),為什么會(huì)輸出0xff ff ff ff?
使用%d輸出,
printf("*( (char*)( (int)(&b)+1 ) ) = %d \n" , *( (char*)( (int)(&b)+1 ) ) ) ;
結(jié)果為-1???
問題出在signed char 的范圍是:-128~127,這樣肯定無法儲(chǔ)存0xff,出現(xiàn)溢出。所以將
printf("*( (char*)( (int)(&b)+1 ) ) = %#x \n" , *( (char*)( (int)(&b)+1 ) ) ) ;
改成
printf("*( (unsigned char*)( (int)(&b)+1 ) ) = %#x \n" ,
*( (unsigned char*)( (int)(&b)+1 ) ) ) ;
就可以輸出0xff,因?yàn)閡nsigned char 的范圍是:0~255(0xff)。