我正在尝试在从 C 函数返回的 32 位有符号整数上创建一个开关。这一切都是通过以下方式为 32 位 Arm 系统编译的:
GOARCH=arm GOOS=linux CC="..." go build
我的代码是:
func ResultToString(value C.int) error {
switch int(value) {
case 1:
return nil
case -1:
return fmt.Errorf("Unknown")
case 0xF3210123:
return fmt.Errorf("Line high")
}
return fmt.Errorf("bad value")
}
我收到的错误是:
pkg/app/conversions.go:200:7: 0xF3210123 (untyped int constant 4079026467) overflows int
十进制的 0xF3210123
是 4_079_026_467
,完全适合 32 位,如 1111 0011 0010 0001 0000 0001 0010 0011
。然而,当我想要签名的表示时,这是未签名的表示(即-215_940_829
)。
我尝试在开关中转换它,但我所有的方法都不起作用(这里尝试了常量
0xF3210124
:
case int(0xF3210124):
给出错误cannot convert 0xF3210124 (untyped int constant 4079026468) to type int
我还尝试将其创建为常规
var
并按照本博客文章中所述的方式进行转换:https://go.dev/blog/constants但这也不起作用:
# Source
var testVal = 0xF3210123
...
case int(testVal):
# Error
cannot use 0xF3210123 (untyped int constant 4079026467) as int value in variable declaration (overflows)
我想直接使用文字来匹配我正在尝试实现的规范。有没有一种方法可以轻松地做到这一点,我只是想念?
--
有效的是使用转换后的常量(如下所示)。但是,我有很多这样的代码,并且再次想使用十六进制文字来轻松进行规格匹配。
# Working example
func ResultToString(value C.int) error {
switch int(value) {
case 1:
return nil
case -1:
return fmt.Errorf("Unknown")
case -215_940_829:
return fmt.Errorf("Line high")
}
return fmt.Errorf("bad value")
}
如果是 32 位值,则始终使用
int32
,因为 Go 中的 int
可以是 32 位或 64 位,具体取决于平台,这会给你带来很多麻烦。
修复输入类型后,有多种修复方法。第一种方法涉及将所有值转换为目标类型的范围。在补码中,N位负值的绝对值和它的表示总和为2N,因此通过像这样加/减2N可以很容易地在它们之间进行转换
func ResultToString_u32(value int32) error {
switch uint32(value) {
case 1:
return nil
case -1 + (math.MaxUint32 + 1): // convert -1 to its representation
// or case -1 + 0x1_0000_0000:
return fmt.Errorf("Unknown")
case 0xF3210123:
return fmt.Errorf("Line high")
}
return fmt.Errorf("bad value")
}
func ResultToString_i32(value int32) error {
switch int32(value) {
case 1:
return nil
case -1:
return fmt.Errorf("Unknown")
case 0xF3210123 - (math.MaxUint32 + 1): // convert 0xF3210123 to its negative value
// or case 0xF3210123 - 0x1_0000_0000:
return fmt.Errorf("Line high")
}
return fmt.Errorf("bad value")
}
它非常高效,因为所有情况仍然是常量。不涉及任何变量,因此可以使用跳转表。当大多数情况值都是无符号且极少数情况为负值时,匹配为
uint32
会更有用。否则就匹配 int32
当然,您也可以编写如下所示的辅助函数,但情况不再恒定,因此输出可能不太优化:
func uint2int(v uint32) int32 {
// return v - (math.MaxUint32 + 1)
// return v - 0x1_0000_0000
return int32(v)
}
func int2uint(v int32) int32 {
// return v + (math.MaxUint32 + 1)
// return v + 0x1_0000_0000
return uint32(v)
}
switch uint32(value) {
case int2uint(-1): // ...
case 0xF3210123: // ...
}
switch int32(value) {
case -1: // ...
case uint2int(0xF3210123): // ...
}
或者只需创建一个变量来存储超出范围的值并单独进行匹配。在这种情况下不要使用无类型常量。您发布的
var testVal = 0xF3210123
导致 untyped int constant 4079026467
错误。你必须使用var testValue uint32 = 0xF3210123
var minus1 int = -1
switch uint32(value) {
case uint32(minus1): // ...
case 0xF3210123: // ...
}
var testVal uint32 = 0xF3210123
switch int32(value) {
case -1: // ...
case int32(testVal): // ...
}
与上面的辅助函数类似,它可能不如第一种方法优化
我用它在我的本地机器上进行了测试
func test(f func(int32) string) {
fmt.Printf("%-15s expected: Bad value\n", f(0))
fmt.Printf("%-15s expected: No error\n", f(1))
fmt.Printf("%-15s expected: Unknown\n", f(-1))
fmt.Printf("%-15s expected: Line high\n", f(0xF3210123-0x1_0000_0000))
fmt.Printf("%-15s expected: Line high\n", f(0xF3210123-(math.MaxUint32+1)))
fmt.Printf("%-15s expected: Line high\n", f(int32(testVal)))
fmt.Printf("%-15s expected: Bad value\n\n", f(0x73210123))
}
func main() {
test(ResultToString1)
test(ResultToString2)
test(ResultToString3)
// ...
}
使用此命令检查
arm
和 arm64
的结果会返回它们与预期相同
git diff --no-index <(GOARCH=arm go run swcase.go | tee /dev/stderr) \
<(GOARCH=arm64 go run swcase.go)
这可以用关于无类型常量的语言规范的特定解释来解释。看起来
0xF3210123
被解释为 uint32
值,然后在编译时转换为 int32
值时会导致溢出。
但是在运行时转换时它不会这样做。
太丑陋了,但可能有一个解决方法(我没有在 32 位系统上测试这个):
var v = 0xF3210124
var x = int32(v)
...
switch ... {
case x: ...
}
我也尝试了一些东西......这个有效:
const (
ZERO int32 = 0
BIG1 = 0xF3210123
BIG2 = 0xFF00FF00
)
及之后:
case BIG1:
fmt.Printf("big\n");
听起来你必须想出很多名字,但有些人会说这比到处都有很多未知常量要好。