我正在使用unsafeCoerce
和Int8
尝试Word8
,我发现了一些令人惊讶的行为(无论如何对我来说)。
Word8
是一个8位无符号数,范围为0-255。 Int8
是带符号的8位数,范围为-128..127。
因为它们都是8位数,所以我认为相互强制转换是安全的,只需返回8位值,就好像它是有符号/无符号的一样。
例如,unsafeCoerce (-1 :: Int8) :: Word8
我希望导致Word8
值为255(因为有符号整数中的位表示-1与无符号整数中的255相同)。 / p>
但是,当我执行强制操作时,Word8
行为很奇怪:
> GHCi, version 7.4.1: http://www.haskell.org/ghc/ :? for help
> import Data.Int
> import Data.Word
> import Unsafe.Coerce
> class ShowType a where typeName :: a -> String
> instance ShowType Int8 where typeName _ = "Int8"
> instance ShowType Word8 where typeName _ = "Word8"
> let x = unsafeCoerce (-1 :: Int8) :: Word8
> show x
"-1"
> typeName x
"Word8"
> show (x + 0)
"255"
> :t x
x :: Word8
> :t (x + 0)
(x + 0) :: Word8
我不明白show x
在这里如何返回"-1"
。如果您查看map show [minBound..maxBound :: Word8]
,则Word8
的可能值不会导致"-1"
。此外,即使类型未更改,如何向数字添加0也会更改行为?奇怪的是,它似乎只有Show
类受到影响 - 我的ShowType
类返回正确的值。
最后,代码fromIntegral (-1 :: Int8) :: Word8
按预期工作,并返回255,并与show
一起正常工作。编译器是否可以将此代码简化为无操作?
请注意,这个问题仅仅是出于对ghc中低级别表示类型的好奇心。我实际上并没在我的代码中使用unsafeCoerce。
答案 0 :(得分:10)
就像@kosmikus所说,Int8
和Int16
都是使用Int#
实现的,在32位架构上是32位宽的(Word8
和{ {1}}是Word16
。 GHC.Prim中的This comment更详细地解释了这一点。
因此,让我们找出为什么这种实现选择会导致您看到的行为:
Word#
> let x = unsafeCoerce (-1 :: Int8) :: Word8
> show x
"-1"
is defined as
Show
个实例
Word8
和fromIntegral
只是instance Show Word8 where
showsPrec p x = showsPrec p (fromIntegral x :: Int)
。 fromInteger . toInteger
toInteger
的定义是
Word8
其中toInteger (W8# x#) = smallInteger (word2Int# x#)
(在integer-gmp中定义)是
smallInteger
和smallInteger :: Int# -> Integer
smallInteger i = S# i
是primop,其类型为word2Int#
- 在C ++中类似于Word# -> Int#
。这就解释了为什么在第一个示例中看到reinterpret_cast<int>
的原因:该值只是重新解释为有符号整数并打印出来。
现在,为什么将-1
添加到0
会给你x
?查看255
的{{1}}个实例,我们看到了这一点:
Num
所以看起来Word8
primop是罪魁祸首。我们来看看:
(W8# x#) + (W8# y#) = W8# (narrow8Word# (x# `plusWord#` y#))
确实如此。这就解释了为什么添加0不是无操作 - narrow8Word#
添加实际上会将值限制在预期范围内。
答案 1 :(得分:4)
当你使用unsafeCoerce
时,你不能说出错了。如果您使用该功能,任何事情都可能发生。编译器可能在单词中存储Int8
,并使用unsafeCoerce
到Word8
打破存储在该单词中的不变量。使用fromIntegral
进行转换。
使用Int8
从Word8
转换为fromIntegral
会在x86上使用ghc转换为movzbl
指令,这基本上是无操作。