在Go语言中查找常量的地址。

huangapple go评论79阅读模式
英文:

Find address of constant in go

问题

我们编写了一个程序,试图找到一个常量的地址。这样做可能吗?

package main

func main() {
        const k = 5
        address := &k
}

它会报错,有人可以告诉我们如何找到一个常量的地址吗?

英文:

We have written one program by which we try to find an address of a constant. Is it possible to do it like that?

package main

func main() {
        const k = 5
        address := &k
}

It gives an error, can anyone tell how can we find the address of a constant?

答案1

得分: 77

简而言之:你不能

错误信息显示:

> 无法获取 k 的地址

对于地址操作符 &,有一些限制。规范:地址操作符

> 对于类型为 T 的操作数 x,地址操作 &x 会生成一个类型为 *T 的指针,指向 x。操作数必须是可寻址的,即变量、指针间接引用或切片索引操作;或者是可寻址结构操作数的字段选择器;或者是可寻址数组的数组索引操作。作为对可寻址要求的例外,x 也可以是(可能带括号的)复合字面量。如果对 x 的求值会导致运行时恐慌,那么对 &x 的求值也会导致恐慌。

常量不在可寻址的列表中,而且在规范中未列为可寻址的事物(如上所引用)不能成为地址操作符 & 的操作数(你不能获取它们的地址)。

不允许获取常量的地址有两个原因:

  1. 常量可能根本没有地址。
  2. 即使在运行时将常量值存储在内存中,这也是为了帮助运行时保持常量的特性。如果可以获取常量值的地址,就可以将地址(指针)赋给变量,并且可以更改该变量(指向的值,常量的值)。Go 语言的作者之一 Robert Griesemer 解释了为什么不允许获取字符串字面量的地址:“如果可以获取字符串常量的地址,就可以调用一个函数(该函数对指向的值进行赋值),可能会产生奇怪的效果 - 你肯定不希望字面字符串常量发生变化。”(来源

如果你需要一个等于该常量的值的指针,请将其赋给一个可寻址的变量,这样你就可以获取它的地址,例如:

func main() {
    const k = 5
    v := k
    address := &v // 这是允许的
}

但要知道,在 Go 语言中,数值常量表示任意精度的值,并且不会溢出。当你将常量的值赋给变量时,可能是不可能的(例如,常量可能大于你要赋给它的变量类型的最大值 - 导致编译时错误),或者可能不相同(例如,对于浮点数常量,可能会丢失精度)。

英文:

In short: you can't.

The error message says:

> cannot take the address of k

There are limitations on the operand of the address operator &. Spec: Address operators:

> For an operand x of type T, the address operation &x generates a pointer of type *T to x. The operand must be addressable, that is, either a variable, pointer indirection, or slice indexing operation; or a field selector of an addressable struct operand; or an array indexing operation of an addressable array. As an exception to the addressability requirement, x may also be a (possibly parenthesized) composite literal. If the evaluation of x would cause a run-time panic, then the evaluation of &x does too.

Constants are not listed as addressable, and things that are not listed in the spec as addressable (quoted above) cannot be the operand of the address operator & (you can't take the address of them).

It is not allowed to take the address of a constant. This is for 2 reasons:

  1. A constant may not have an address at all.
  2. And even if a constant value is stored in memory at runtime, this is to help the runtime to keep constants that: constant. If you could take the address of a constant value, you could assign the address (pointer) to a variable and you could change that (the pointed value, the value of the constant). Robert Griesemer (one of Go's authors) wrote why it's not allowed to take a string literal's address: "If you could take the address of a string constant, you could call a function [that assigns to the pointed value resulting in] possibly strange effects - you certainly wouldn't want the literal string constant to change." (source)

If you need a pointer to a value being equal to that constant, assign it to a variable of which is addressable so you can take its address, e.g.

func main() {
    const k = 5
    v := k
    address := &v // This is allowed
}

But know that in Go numeric constants represent values of arbitrary precision and do not overflow. When you assign the value of a constant to a variable, it may not be possible (e.g. the constant may be greater than the max value of the variable's type you're assigning it to - resulting in compile-time error), or it may not be the same (e.g. in case of floating point constants, it may lose precision).

答案2

得分: 41

我在单元测试期间创建大型嵌套的 JSON 对象时经常遇到这个问题。我可能有一个结构,其中所有字段都是指向字符串/整数的指针:

type Obj struct {
    Prop1  *string
    Prop2  *int
    Status *string
}

我想要这样写:

obj := Obj{
    Prop1:  &"a string property",
    Prop2:  &5,
    Status: &statuses.Awesome,
}

但是语言不允许直接这样做。绕过这个问题的一种快速方法是定义一个函数,该函数接受一个常量并返回其地址:

s := func(s string) *string { return &s }
i := func(i int) *int { return &i }

obj := Obj{
    Prop1:  s("a string property"),
    Prop2:  i(5),
    Status: s(statuses.Awesome),
}

这是因为当常量作为参数传递给函数时,会创建常量的副本,这意味着函数中创建的指针不指向常量的地址,而是指向其副本的地址,就像将常量值分配给 var 时一样。然而,使用函数来做这个比起不得不提前声明大块的变量来说更易读/不那么繁琐。

AWS SDK 使用了这种技术。我现在经常在我的项目中添加一个类似以下的包:

package ref

import "time"

func Bool(i bool) *bool {
    return &i
}

func Int(i int) *int {
    return &i
}

func Int64(i int64) *int64 {
    return &i
}

func String(i string) *string {
    return &i
}

func Duration(i time.Duration) *time.Duration {
    return &i
}

func Strings(ss []string) []*string {
    r := make([]*string, len(ss))
    for i := range ss {
        r[i] = &ss[i]
    }
    return r
}

我以以下方式调用它:

func (t Target) assignString(to string, value string) {
    if to == tags.AuthorityId {
        t.authorityId = ref.String(value)
    }
    // ...
}

你还可以添加一个 deref 包,不过我通常发现它不太有用:

package deref

func String(s *string, d string) string {
    if s != nil { return *s }
    return d
}

// 更多的解引用函数。

2022 年 4 月编辑:

随着 go 1.18 的发布,现在可以定义一个处理从常量到指针的所有转换的单个方法:

package ref

func Of[E any](e E) *E {
    return &e
}
英文:

I often hit this problem when creating large, nested JSON objects during unit tests. I might have a structure where all the fields are pointers to strings/ints:

type Obj struct {
	Prop1  *string
	Prop2  *int
    Status *string
}

and want to write something like:

obj := Obj{
    Prop1:  &"a string property",
    Prop2:  &5,
    Status: &statuses.Awesome,
}

When I initialise it, but the language doesn't allow this directly. A quick way to bypass this is to define a function that takes a constant and returns its address:

 s := func(s string) *string { return &s }
 i := func(i int) *int { return &i }

 obj := Obj{
    Prop1:  s("a string property"),
    Prop2:  i(5),
    Status: s(statuses.Awesome)
}

This works due to the fact that when the constant is passed as a parameter to the function, a copy of the constant is made which means the pointer created in the function does not point to the address of the constant, but to the address of its copy, in the same way as when a constant value is assigned to a var. However, using a function to do this makes it more readable/less cumbersome IMO than having to forward declare large blocks of variables.

The AWS SDK uses this technique. I now find myself regularly adding a package to my projects that looks something like:

package ref

import "time"

func Bool(i bool) *bool {
	return &i
}

func Int(i int) *int {
	return &i
}

func Int64(i int64) *int64 {
	return &i
}

func String(i string) *string {
	return &i
}

func Duration(i time.Duration) *time.Duration {
	return &i
}

func Strings(ss []string) []*string {
	r := make([]*string, len(ss))
	for i := range ss {
		r[i] = &ss[i]
	}
	return r
}

Which I call in the following way:

func (t: Target) assignString(to string, value string) {
	if to == tags.AuthorityId {
		t.authorityId = ref.String(value)
    }
    // ...
}

You can also add a deref package, though I have generally found this to be less useful:

package deref

func String(s *string, d string) string {
	if s != nil { return *s }
	return d
}

// more derefs here.

EDIT April 2022:

With the release of go 1.18, it's now possible to define a single method to handle all conversions from constants into pointers:

package ref

func Of[E any](e E) *E {
	return &e
}

答案3

得分: 2

这3个选项可能会有帮助:

  1. 使用带有泛型的辅助函数。(适用于原始类型和自定义类型)
package main

import "fmt"

type Role string

const (
	Engineer  Role = "ENGINEER"
	Architect Role = "ARCHITECT"
)

const (
	EngineerStr  string = "ENGINEER"
	ArchitectStr string = "ARCHITECT"
)

func main() {
	fmt.Println(PointerTo(Engineer))    // 适用于自定义类型
	fmt.Println(PointerTo(EngineerStr)) // 适用于原始类型
}

func PointerTo[T any](v T) *T {
	return &v
}

在 playground 上试一试

  1. 使用 pointy。(仅适用于原始类型)

  2. 使用 ToPointer() 方法。(仅适用于自定义类型)

package main

import "fmt"

type Role string

const (
	Engineer  Role = "ENGINEER"
	Architect Role = "ARCHITECT"
)

func (r Role) ToPointer() *Role {
	return &r
}

func main() {
	fmt.Println(Engineer.ToPointer())
}

在 playground 上试一试

英文:

These 3 options could be helpful:

  1. Using a helper function with generics. (Works for both primitive and custom types)
package main

import "fmt"

type Role string

const (
	Engineer  Role = "ENGINEER"
	Architect Role = "ARCHITECT"
)

const (
	EngineerStr  string = "ENGINEER"
	ArchitectStr string = "ARCHITECT"
)

func main() {
	fmt.Println(PointerTo(Engineer))    // works for custom types
	fmt.Println(PointerTo(EngineerStr)) // works for primitive types
}

func PointerTo[T any](v T) *T {
	return &v
}

Try it on playground

  1. Using pointy. (Works only for primitive types)

  2. Using a ToPointer() method. (Works only for custom types)

package main

import "fmt"

type Role string

const (
	Engineer  Role = "ENGINEER"
	Architect Role = "ARCHITECT"
)

func (r Role) ToPointer() *Role {
	return &r
}

func main() {
	fmt.Println(Engineer.ToPointer())
}

Try it on playground

答案4

得分: 1

我找到了另一种处理这个问题的方法,就是使用AWS API:

import "github.com/aws/aws-sdk-go/aws"

type Obj struct {
    *int
}

x := aws.Int(16) // 返回地址
obj := Obj{x} // 正常工作

这种方法与上面的答案基本相同,但你不必自己编写整个函数。

参考:https://docs.aws.amazon.com/sdk-for-go/api/aws/

英文:

I found another way to deal with this, which is using AWS API:

import "github.com/aws/aws-sdk-go/aws"

type Obj struct {
    *int
}

x := aws.Int(16) // return address
obj := Obj{x} // work fine

this method is literally same as the answer above, but you dont have to write the whole functions on your own.

See: https://docs.aws.amazon.com/sdk-for-go/api/aws/

答案5

得分: -3

constants 部分并没有很清楚地说明:常量与变量不同,它们不会出现在编译后的代码或运行的程序中。它们是无类型的,只有在被赋值给变量后才会存在于内存中。

因此,它们似乎具有无限的精度。如果你看一下这个例子,你会发现我可以将常量赋值给一个变量而无需进行类型转换,变量将尽可能地保留常量的精度。


<sup>1</sup><small> 正如规范中也指出的,整数至少有256位,浮点数至少有256位的尾数和32位的指数,如果编译器的内部结构无法准确存储常量,编译器将会抛出错误。
</small>

英文:

What the constants section does not make very clear: Constants are, unlike variables, not present in the compiled code or running program. They are untyped and will only be in memory once they are assigned to a variable.

As a result, they seem<sup>1</sup> to have infinite precision. If you look at this example, you can see that I can assign the constant to a variable without casting it, and the variable will hold as much of the constants precision as it can.


<sup>1</sup><small> As the spec also points out, integers have at least 256 bits, floats at least 256 bits mantissa and 32 bits exponent, and the compiler will throw an error if its internal constructs cannot accurately store a constant.
</small>

huangapple
  • 本文由 发表于 2016年2月2日 14:11:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/35146286.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定