Golang 非类型 int 在 32 位 Arm 上溢出

问题描述 投票:0回答:3

我正在尝试在从 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")
}
linux go arm embedded-linux cgo
3个回答
0
投票

如果是 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)

这是包含所有测试用例的完整演示


-1
投票

这可以用关于无类型常量的语言规范的特定解释来解释。看起来

0xF3210123
被解释为
uint32
值,然后在编译时转换为
int32
值时会导致溢出。

但是在运行时转换时它不会这样做。

太丑陋了,但可能有一个解决方法(我没有在 32 位系统上测试这个):

var v = 0xF3210124
var x = int32(v)

...

switch ... {
   case x: ...
}

-1
投票

我也尝试了一些东西......这个有效:

const (
        ZERO int32 = 0
        BIG1 = 0xF3210123
        BIG2 = 0xFF00FF00
)

及之后:

    case BIG1:
        fmt.Printf("big\n");

听起来你必须想出很多名字,但有些人会说这比到处都有很多未知常量要好。

© www.soinside.com 2019 - 2024. All rights reserved.