web-dev-qa-db-fra.com

Utilisation de pointeurs dans une boucle for

J'ai du mal à comprendre pourquoi j'ai un bogue dans mon code dans un état mais pas dans l'autre. Cela fait un moment que je n'ai pas couvert les pointeurs, donc je suis probablement rouillé!

Fondamentalement, j'ai une structure de référentiel que j'utilise pour stocker un objet en mémoire, qui a une fonction Store.

type chartsRepository struct {
    mtx    sync.RWMutex
    charts map[ChartName]*Chart
}

func (r *chartsRepository) Store(c *Chart) error {
    r.mtx.Lock()
    defer r.mtx.Unlock()
    r.charts[c.Name] = c
    return nil
}

Il suffit donc de mettre un verrou mutex RW et d'ajouter le pointeur à une carte, référencée par un identifiant.

Ensuite, j'ai une fonction qui va essentiellement parcourir une tranche de ces objets, les stockant tous dans le référentiel.

type service struct {
    charts Repository
}

func (svc *service) StoreCharts(arr []Chart) error {
    hasError := false
    for _, chart := range arr {
        err := svc.repo.Store(&chart)
        // ... error handling
    }
    if hasError {
        // ... Deals with the error object
        return me
    }
    return nil
}

Ce qui précède ne fonctionne pas, il semble que tout fonctionne bien au début, mais en essayant d'accéder aux données plus tard, les entrées de la carte pointent toutes vers le même objet Chart, malgré des clés différentes.

Si je fais ce qui suit et déplace la référence du pointeur vers une autre fonction, tout fonctionne comme prévu:

func (svc *service) StoreCharts(arr []Chart) error {
    // ...
    for _, chart := range arr {
        err := svc.storeChart(chart)
    }
    // ...
}

func (svc *service) storeChart(c Chart) error {
    return svc.charts.Store(&c)
}

Je suppose que le problème est que, car la boucle remplace la référence à chart dans la boucle for, la référence du pointeur change également. Lorsque le pointeur est généré dans une fonction indépendante, cette référence n'est jamais remplacée. Est-ce correct?

J'ai l'impression d'être stupide, mais le pointeur ne devrait-il pas être généré par &chart et cela est indépendant de la référence chart? J'ai également essayé de créer une nouvelle variable pour le pointeur p := &chart dans la boucle for et cela n'a pas fonctionné non plus.

Dois-je simplement éviter de générer des pointeurs en boucle?

17
Simon

En effet, il n'y a qu'une seule variable de boucle chart et, à chaque itération, seule une nouvelle valeur lui est affectée. Donc, si vous essayez de prendre l'adresse de la variable de boucle, ce sera la même à chaque itération, donc vous stockerez le même pointeur, et l'objet pointé (la variable de boucle) est écrasé à chaque itération (et après la boucle, il contiendra la valeur attribuée lors de la dernière itération).

Ceci est mentionné dans Spec: pour les instructions: pour les instructions avec la clause range:

Les variables d'itération peuvent être déclarées par la clause "range" en utilisant une forme de déclaration de variable courte (:=). Dans ce cas, leurs types sont définis sur les types des valeurs d'itération respectives et leur portée est le bloc de l'instruction "for"; ils sont réutilisés à chaque itération . Si les variables d'itération sont déclarées en dehors de l'instruction "for", après exécution, leurs valeurs seront celles de la dernière itération.

Votre deuxième version fonctionne, car vous passez la variable de boucle à une fonction, donc une copie en sera faite, puis vous stockez l'adresse de la copie (qui est détachée de la variable de boucle).

Vous pouvez obtenir le même effet sans fonction: créez simplement une copie locale et utilisez l'adresse de celle-ci:

for _, chart := range arr {
    chart2 := chart
    err := svc.repo.Store(&chart2) // Address of the local var
    // ... error handling
}

Notez également que vous pouvez également stocker l'adresse des éléments de tranche:

for i := range arr {
    err := svc.repo.Store(&arr[i]) // Address of the slice element
    // ... error handling
}

L'inconvénient de cela est que, puisque vous stockez des pointeurs vers les éléments de tranche, l'ensemble du tableau de sauvegarde de la tranche devrait être conservé en mémoire tant que vous gardez l'un des pointeurs (le tableau ne peut pas être récupéré). De plus, les pointeurs que vous stockez partageraient les mêmes valeurs Chart que la tranche, donc si quelqu'un modifiait une valeur de graphique de la tranche passée, cela affecterait les graphiques dont vous avez stocké les pointeurs.

Voir les questions connexes:

Golang: enregistrer plusieurs itinéraires en utilisant la plage pour les tranches de boucle/carte

Pourquoi ces deux variations de boucle me donnent-elles un comportement différent?

27
icza