web-dev-qa-db-fra.com

Crash lors de la conversion du résultat de arc4random () en Int

J'ai écrit un simple cours sur les sacs. Un sac est rempli avec un rapport fixe d'ensembles de température. Il vous permet d’en prendre un au hasard et de se remplir automatiquement s’il est vide. Cela ressemble à ceci:

class Bag {
    var items = Temperature[]()

    init () {
        refill()
    }

    func grab()-> Temperature {
        if items.isEmpty {
            refill()
        }

        var i = Int(arc4random()) % items.count
        return items.removeAtIndex(i)
    }

    func refill() {
        items.append(.Normal)

        items.append(.Hot)
        items.append(.Hot)

        items.append(.Cold)
        items.append(.Cold)
    }
}

L'énumération de température ressemble à ceci:

enum Temperature: Int {
    case Normal, Hot, Cold
}

Mon GameScene:SKScene a une propriété d'instance constante bag:Bag. (J'ai aussi essayé avec une variable.) Quand j'ai besoin d'une nouvelle température, j'appelle bag.grab(), une fois dans didMoveToView et lorsque cela est approprié dans touchesEnded.

Au hasard, cet appel se bloque sur la ligne if items.isEmpty dans Bag.grab(). L'erreur est EXC_BAD_INSTRUCTION. La vérification du débogueur montre que les éléments sont size=1 et [0] = (AppName.Temperature) <invalid> (0x10).

Edit On dirait que je ne comprends pas les informations du débogueur. Même les tableaux valides affichent size=1 et des valeurs non liées pour [0] =. Donc, aucune aide là-bas.

Je n'arrive pas à le faire planter isolé dans un terrain de jeu. C'est probablement quelque chose d'évident mais je suis perplexe.

25
Shaun Inman

La fonction arc4random renvoie un UInt32. Si vous obtenez une valeur supérieure à Int.max, la distribution Int(...) se bloquera.

En utilisant

Int(arc4random_uniform(UInt32(items.count)))

devrait être une meilleure solution.

(Blame les messages de crash étranges dans la version Alpha ...)

54
Sulthan

J'ai trouvé que le meilleur moyen de résoudre ce problème est d'utiliser Rand () au lieu de arc4random ()

le code, dans votre cas, pourrait être:

var i = Int(Rand()) % items.count
2
vyudi

Cette méthode générera une valeur Int aléatoire entre le minimum et le maximum donnés

func randomInt(min: Int, max:Int) -> Int {
    return min + Int(arc4random_uniform(UInt32(max - min + 1)))
}

Le crash que vous avez rencontré est dû au fait que Swift a détecté une incohérence de type lors de l'exécution. Depuis Int! = UInt32, vous devrez d'abord transtyper l'argument d'entrée de arc4random_uniform avant de pouvoir calculer le nombre aléatoire.

1
Groot

Swift ne permet pas de transtyper d'un type entier à un autre si le résultat de la conversion ne correspond pas. Par exemple. le code suivant fonctionnera bien:

let x = 32
let y = UInt8(x)

Pourquoi? Parce que 32 est une valeur possible pour un int de type UInt8. Mais le code suivant va échouer:

let x = 332
let y = UInt8(x)

En effet, vous ne pouvez pas affecter 332 à un type non signé de 8 bits, il ne peut prendre que les valeurs de 0 à 255 et rien d’autre.

Lorsque vous effectuez un casting en C, l'int est simplement tronqué, ce qui peut être inattendu ou non souhaité, car le programmeur peut ne pas être conscient du fait que la troncature peut avoir lieu. Donc, Swift gère les choses un peu différentes ici. Cela autorisera ce type d'incantation tant qu'aucune troncation n'a lieu, mais s'il y a troncation, vous obtenez une exception d'exécution. Si vous pensez que la troncature est acceptable, vous devez le faire vous-même pour que Swift sache qu'il s'agit du comportement souhaité, sinon Swift doit supposer qu'il s'agit d'un comportement accidentel.

Ceci est même documenté (documentation de UnsignedInteger):

Convertissez à partir du type entier non signé le plus large de Swift, le recouvrement lors du débordement.

Et ce que vous voyez, c'est le "piégeage par débordement", qui est mal fait car, bien sûr, on aurait pu faire en sorte que ce piège explique réellement ce qui se passe.

En supposant que items ne comporte jamais plus de 2 ^ 32 éléments (un peu plus de 4 milliards), le code suivant est sécurisé:

var i = Int(arc4random() % UInt32(items.count))

S'il peut avoir plus de 2 ^ 32 éléments, vous rencontrez un autre problème car vous avez besoin d'une fonction de nombre aléatoire différente qui produit des nombres aléatoires au-delà de 2 ^ 32.

1
Mecki

Ce crash n'est possible que sur les systèmes 32 bits. Int change entre 32 bits (Int32) et 64 bits (Int64) en fonction de l'architecture du périphérique ( voir la documentation ).

Le maximum de UInt32 est 2^32 − 1. Le maximum de Int64 est 2^63 − 1, donc Int64 peut facilement gérer UInt32.max. Cependant, le nombre maximal de Int32 est 2^31 − 1, ce qui signifie que UInt32 peut gérer des nombres supérieurs à Int32 et que la création d'un Int32 à partir d'un nombre supérieur à 2^31-1 entraînera un débordement.

J'ai confirmé cela en essayant de compiler la ligne Int(UInt32.max). Sur les simulateurs et les appareils plus récents, la compilation est parfaite. Mais j'ai connecté mon ancien iPod Touch (appareil 32 bits) et j'ai eu cette erreur de compilation:

Débordement d'entier lors de la conversion de UInt32 en Int

Xcode ne compilera même pas cette ligne pour les périphériques 32 bits, ce qui est probablement le crash qui se produit au moment de l'exécution. Beaucoup des autres réponses de cet article sont de bonnes solutions, je ne les ajouterai donc pas et ne les copierai pas. Je pensais simplement qu'il manquait à cette question une explication détaillée de ce qui se passait.

1
keithbhunter

Vous pouvez utiliser

Int(Rand())

Pour éviter les mêmes nombres aléatoires au démarrage de l'application, vous pouvez appeler srand ()

srand(UInt32(NSDate().timeIntervalSinceReferenceDate))
let randomNumber: Int = Int(Rand()) % items.count
0
Ivan

Cela créera automatiquement un Int aléatoire pour vous:

var i = random() % items.count

i est de type Int, aucune conversion n'est donc nécessaire!

0
NatashaTheRobot