Maison  >  Article  >  développement back-end  >  Regarder! Une erreur stupide que vous ne pouvez pas commettre lorsque vous utilisez Go struct !

Regarder! Une erreur stupide que vous ne pouvez pas commettre lorsque vous utilisez Go struct !

藏色散人
藏色散人avant
2021-06-19 15:53:081809parcourir

La colonne tutorielle suivante de golang vous présentera une erreur de bas niveau qui ne peut pas être commise lors de l'utilisation de Go struct. J'espère qu'elle sera utile aux amis dans le besoin !

Une erreur stupide que vous ne pouvez pas commettre lorsque vous utilisez Go struct !

Cet article GitHub github.com/eddycjy/blog a été inclus

Bonjour à tous, je m'appelle Jianyu.

Il y a quelque temps, j'ai partagé « Shandou Go Interviewer : Les structures de Go peuvent-elles être comparées ? Pourquoi ? 》 article, a étudié la base de comparaison de la structure Go de base. Non, récemment, un lecteur a rencontré un nouveau problème concernant la structure qu'il n'a pas pu résoudre.

Jetons un coup d'œil ensemble. Il est recommandé de réfléchir à la réponse après avoir vu l'exemple de code avant de lire ci-dessous.

Il est important de penser de manière indépendante.

Exemple de confusion

L'exemple donné est le suivant :

type People struct {}

func main() {
 a := &People{}
 b := &People{}
 fmt.Println(a == b)
}

Que pensez-vous du résultat de sortie ?

Le résultat de sortie est : faux.

Une petite modification, l'exemple 2 est le suivant :

type People struct {}

func main() {
 a := &People{}
 b := &People{}
 fmt.Printf("%p\n", a)
 fmt.Printf("%p\n", b)
 fmt.Println(a == b)
}

Le résultat de sortie est : true.

Sa question est "Pourquoi le premier renvoie-t-il faux et le second renvoie vrai ? Quelle est la raison ?

En simplifiant davantage cet exemple, nous obtenons un exemple minimal :

func main() {
    a := new(struct{})
    b := new(struct{})
    println(a, b, a == b)

    c := new(struct{})
    d := new(struct{})
    fmt.Println(c, d)
    println(c, d, c == d)
}

Résultat de sortie :

// a, b; a == b
0xc00005cf57 0xc00005cf57 false

// c, d
&{} &{}
// c, d, c == d
0x118c370 0x118c370 true

Le résultat du premier paragraphe de code est faux, le résultat du deuxième paragraphe est vrai, et vous pouvez voir que l'adresse mémoire pointe exactement vers le pareil, c'est la cause du changement dans le pointeur de mémoire variable après l'exclusion de la sortie

En regardant plus loin, cela semble être causé par la méthode fmt.Print, mais par une méthode de sortie dans la bibliothèque standard. va causer cet étrange problème ?

Analyse du problème

Si vous avez déjà été "piégé" par cela, ou si vous avez lu le code source, vous pourrez peut-être rapidement vous rendre compte que la sortie est causée par analyse d'échappement

Nous effectuons une analyse d'échappement sur l'exemple :

// 源代码结构
$ cat -n main.go
     5    func main() {
     6        a := new(struct{})
     7        b := new(struct{})
     8        println(a, b, a == b)
     9    
    10        c := new(struct{})
    11        d := new(struct{})
    12        fmt.Println(c, d)
    13        println(c, d, c == d)
    14    }

// 进行逃逸分析
$ go run -gcflags="-m -l" main.go
# command-line-arguments
./main.go:6:10: a does not escape
./main.go:7:10: b does not escape
./main.go:10:10: c escapes to heap
./main.go:11:10: d escapes to heap
./main.go:12:13: ... argument does not escape

Grâce à l'analyse, nous pouvons savoir que les variables a et b sont allouées sur la pile, tandis que les variables c et d sont allouées sur le tas.

La raison principale est que la méthode fmt.Println est appelée, ce qui implique un grand nombre d'appels de méthode liés à la réflexion, ce qui provoquera un comportement d'échappement, c'est-à-dire

est alloué sur le tas. Pourquoi sont-ils égaux après s'être échappés ?

Concentrez-vous sur le premier détail, qui est "Pourquoi deux structures vides sont-elles égales après s'être échappées ?" ".

Ceci est principalement lié à un détail d'optimisation du runtime Go, comme suit :

// runtime/malloc.go
var zerobase uintptr

La variable zerobase est l'adresse de base de toutes les allocations de 0 octet. De plus, elle est vide ( 0 octets) après l'analyse d'échappement, l'allocation du tas pointera vers l'adresse zerobase

, donc la structure vide pointe essentiellement vers

après l'échappement. La comparaison entre les deux est vraie. est renvoyé. zerobase

Pourquoi ne sont-ils pas égaux sans s'échapper ?

Concentrez-vous sur le deuxième détail, qui est "Pourquoi les deux structures vides ne sont-elles pas égales avant de s'échapper ?" ”.

Go spec

D'après les spécifications Go, il s'agit d'une conception délibérée par l'équipe Go, et nous ne voulons pas que tout le monde s'y fie car une base de jugement. . Comme suit :

Il s'agit d'un choix de langage intentionnel pour donner aux implémentations une flexibilité dans la façon dont elles gèrent les pointeurs vers des objets de taille nulle si chaque pointeur vers un objet de taille nulle était requis. pour être différent, alors chaque allocation d'un objet de taille nulle devrait allouer au moins un octet si chaque pointeur vers un objet de taille nulle devait être le même, il serait différent de gérer la prise de l'adresse d'un zéro. -sized au sein d'une structure plus grande.
dit également une description très classique et détaillée :

Les pointeurs vers des variables distinctes de taille nulle peuvent ou non être égaux.
De plus, il existe relativement peu de scénarios d'utilisation réelle de structures vides. Les plus courants sont :

    Définir le contexte, qui est utilisé lors du passage en tant que clé.
  • Set empty. struct est temporairement utilisé dans les scénarios commerciaux
  • Mais dans les scénarios commerciaux, la plupart d'entre eux continueront à changer avec le développement de l'entreprise. s'appuie sur une structure vide. Ne serait-ce pas un accident de juger directement ?

Ne peut pas être invoqué directement

Par conséquent, le fonctionnement de l'équipe Go est exactement le même que le caractère aléatoire de la carte Go. Il vaut la peine d'éviter que les gens s'appuient directement sur ce type de logique.

Dans le scénario où il n'y a pas d'échappatoire, ce que vous pensez être une véritable comparaison a été directement optimisé pendant la phase d'optimisation du code et transformé en. false. 🎜>

Par conséquent, même s'il semble que == compare dans le code, en fait, lorsque le résultat est a == b, il est directement converti en false et il n'est pas nécessaire de comparer.

Tu ne trouves pas que c'est merveilleux ?

Il n'y a pas d'échappatoire pour le rendre égal

Maintenant qu'on sait qu'il est optimisé lors de la phase d'optimisation du code, alors en revanche, si on connaît le principe, on peut aussi utiliser gcflags pendant allez à la compilation et aux instructions d'exécution pour l'empêcher d'optimiser.

在运行前面的例子时,执行 -gcflags="-N -l" 指令:

$ go run -gcflags="-N -l" main.go 
0xc000092f06 0xc000092f06 true
&{} &{}
0x118c370 0x118c370 true

你看,两个比较的结果都是 true 了。

总结

在今天这篇文章中,我们针对 Go 语言中的空结构体(struct)的比较场景进行了进一步的补全。经过这两篇文章的洗礼,你会更好的理解 Go 结构体为什么叫既可比较又不可比较了。

而空结构比较的奇妙,主要原因如下:

  • 若逃逸到堆上,空结构体则默认分配的是 runtime.zerobase 变量,是专门用于分配到堆上的 0 字节基础地址。因此两个空结构体,都是 runtime.zerobase,一比较当然就是 true 了。
  • 若没有发生逃逸,也就分配到栈上。在 Go 编译器的代码优化阶段,会对其进行优化,直接返回 false。并不是传统意义上的,真的去比较了。

不会有人拿来出面试题,不会吧,为什么 Go 结构体说可比较又不可比较?

若有任何疑问欢迎评论区反馈和交流,最好的关系是互相成就,各位的点赞就是煎鱼创作的最大动力,感谢支持。

文章持续更新,可以微信搜【脑子进煎鱼了】阅读,回复【000】有我准备的一线大厂面试算法题解和资料;本文 GitHub github.com/eddycjy/blog 已收录,欢迎 Star 催更。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer