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