2015年5月6日 星期三

[轉] 使用變數型別的良好習慣

最近在把老人寫的code 在x86_64 上做測試

才深刻體會原來寫一個好程式 要注意 data type 的使用


在32bit 機器上
#include <stdio.h>
int main()
{
        int i = 10;
        int *p = &i;
        int pp = (int)p;
        return 0;
}


gcc 不會warning 

但同樣程式在64bit 機器上跑
cast.c:7:20: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
           int pp = (int)p;


這是warning 理論上你可以不用管他
但是這很危險 可能會產生出很多莫名其妙的bug  ~~
所以編譯程式最好加上 -Werror  
把warning 都當成error  處理
迫使你寫的程式沒有額外的風險

回到正題
在64 bit上要解決這個warning or error
要改成

#include <stdio.h>
int main()
{
          int i = 10;
          int *p = &i;
          long pp = (long)p;
          return 0;
}



為什麼?  因為32bit 跟64bit 機器上pointer 長度不同啊
一個是32 bit 一個是64 bit  
所以把64bit 資料強迫轉型成32 bit integer 當然要警告你 可能會lost data 

當然你可以下面這種方式來解決
if machine32
  int pp = (int)p;
elif machine64
 long pp = (long)p;
endif


但這太麻煩了, stdint.h 內定義了 intptr_t 這個type
當你需要把pointer 轉型成 integer 的時候 你不確定是32 or 64bit
就可以使用intptr_t

#include <stdio.h>
#include <stdint.h>
int main()
{
        int i = 10;
        int *p = &i;
        intptr_t pp = (intptr_t)p;
        return 0;
}


所以 下面這三個打印數值是相同的
#include <stdio.h>
#include <stdint.h>
int main()
{
          int i = 10;
          int *p = &i;
          intptr_t pp = (intptr_t)p;
          printf("%p\n", p);
          printf("%lx\n", pp);
          printf("%p\n", &i);
          return 0;
}

 來看看stdint.h 怎樣定義intptr_t

/* Types for `void *' pointers.  */
#if __WORDSIZE == 64
# ifndef __intptr_t_defined
typedef long int                intptr_t;
#  define __intptr_t_defined
# endif
typedef unsigned long int       uintptr_t;
#else
# ifndef __intptr_t_defined
typedef int                     intptr_t;
#  define __intptr_t_defined
# endif
typedef unsigned int            uintptr_t;
#endif
   

偵測到是64bit 
intptr_t 就是long int  ( 64位元的long int 是64bit, 但32位元的long int是32bit)
否則就是 int   ( 64位元的 int 是32bit, 32位元的int 也是32bit)

================================

同樣的, 關於portability 還有幾點需要介紹
那就是primitive data type
                    32            64
char             1               1
short            2               2
int                4               4
long             4               8
long long     8               8
pointer         4               8

可以注意到 long, and pointer type 是有差異的
所以long 必不portable
所以stdint.h 內也定義了

 35 #ifndef __int8_t_defined
 36 # define __int8_t_defined
 37 typedef signed char             int8_t;
 38 typedef short int               int16_t;
 39 typedef int                     int32_t;
 40 # if __WORDSIZE == 64
 41 typedef long int                int64_t;
 42 # else
 43 __extension__
 44 typedef long long int           int64_t;
 45 # endif
 46 #endif
 47

如果你明確要使用64位元的整數 請使用int64_t
可以看到int64_t 在64bit machine 是long int
而32bit machine 要用long long int

================= size_t =================
看看下面這個小程式

  1 #include <stdio.h>
  2 #include <string.h>
  3
  4 void main()
  5 {
  6         int i = -1;
  7         if (i > strlen ("hello"))
  8                 printf("hello\n");
  9 }

-1 > 5 ?  
yes, 確實會印出hello
這裡的問題是 i 是signed , 用來跟strlen 傳回的size_t (unsigned ) 做比較
會把signed i 轉unsigned 
(unsigned) (-1) 會大於 5

再回來review code
當我們要知道元素大小的時候 我們可以確定至少是0以上
所以應該要用unsigned 來表示
但我們卻常常為了方便而使用int

所以size_t 定義成unsigned integer 
而且是平台相關的
在64bit machine  size_t 是unsigned long
但在32bit machine size_t 是unsigned int


==================ptrdiff_t ================
同樣的 用來表示pointer 之間的difference
但是是signed integer
64bit machine => long
32bit machine => int

沒有留言:

張貼留言