Attention aux NSUInteger

J'avais déjà  remarqué que les NSUInterger n'aiment pas les soustractions.


 


Mais, ce qui suit, je n'aurais jamais pensé !!!



NSUInteger index = 0 ;

if (index > -1)
{
NSLog(@OK) ;
}
else
{
NSLog(@This is so tricky) ;
}

J'aime bien les NSUInteger car ils s'intègrent bien au framework cocoa. Par la suite, je pense que j'adopterais le pattern suivant :



- (void)methodWith:(NSUInteger)_aNumber
{
NSInteger aNumber = _aNumber ;
// use only aNumber, not _aNumber
}

Réponses

  • Alors, oui et non.


    Un NSUInteger, is unsigned. Du coup, il "boucle" sur du positif, plutôt que de passer à  du négatif, où un de ses bits lui dit qu'il est négatif ou positif.


    Par exemple, vu que j'ai remarqué ce cas là  quand j'ai récupéré un commit tout à  l'heure au boulot de mon collègue qui développe avec un iPad 2 (32Bits) et qui ne voit donc pas les warnings liés au 64 bits, que XCode t'avertit si tu fais if (monNSUInteger > 0) que ça sera vrai dans tous les cas.


  • Le problème avec ton "pattern", c'est que si _aNumber est très grand (bit de poids le plus fort à  1), la conversion en NSInteger va générer un nombre négatif...


     


    Non, franchement, NSUInteger (et les scalaires non signés en général) ne posent aucun problème (même pas pour les soustractions) si on fait attention à  ce que l'on fait...

  • C'est un héritage du C, les types débordent. Il faut en tenir compte. 


    Donc le seul pattern valable c'est



    if (a > b)
    c = a - b
    else
    c = 0

    En Swift (ou en C++), grâce à  la surcharge des opérateurs, tu peux développer un type unsigned qui ne déborde pas.

  • AliGatorAliGator Membre, Modérateur


    NSUInteger index = 0 ;

    if (index > -1)
    {
    ...
    }

    D'ailleurs normalement si tu écris ce code là , le compilateur doit t'alerter (du moins moi j'ai activé le warning quand on compare des types de signes différents donc je l'ai) en te disant que tu compares un NSUInteger avec un NSInteger, en tout cas un type non-signé (NSUInteger = unsigned) " donc qui ne peut contenir que des nombres positifs " avec un nombre négatif, et que du coup ton code n'est pas cohérent, forcément. Car pour le compilateur, stricto-sensu, un NSUInteger étant par définition forcément positif, il est forcément > -1.

    Du coup si tu laisses ce code, soit il va convertir -1 en unsigned, et interpréter un nombre négatif comme un un unsigned va l'interpréter, à  cause de l'underflow, comme un nombre super grand... soit il va convertir ton NSUInteger en signé (NSInteger), et du coup si ton nombre est super grand il va l'interpréter comme un nombre négatif. Dans tous les cas c'est pas cohérent. Faut pas comparer des patates et des carottes, même si ce sont tous les deux des légumes.
  • Pas de warning !

    Et la réponse qu'il me donne est "It is so tricky". En pratique, les entiers que j'utilise ne sont jamais très grands. Je compte des vues, etc.
  • AliGatorAliGator Membre, Modérateur
    Bah dans ces cas là  si tu comptes des vues, et qu'il te retourne un NSUInteger puisqu'un nombre de vues ne peut être que positif et jamais négatif, pourquoi tester si > -1 ?
  • Je travaille avec des index, de type NSUInteger.


     


    J'ai une @property NSInteger maxIndex. Elle vaut -1 s'il n'y a aucun index.


    Dans une des méthodes qui prend en paramètre un index (NSUInteger), je teste



    if (index < self.maxIndex)
  • Ben c'est ce que se passe quand on compare des carottes a des navets...



    Il me semble qu'en C les operateurs binaires de comparaison travaillent justement en binaire. Les litéraux sont donc utilisés tels quels.

    Pour rappel sur 32bit -1 fait :



    0b01111111 11111111 11111111 11111111

     

    soit 9223372036854775807 quand on le considère comme un entier non signé...

    (pas de bit de signe mais le fameux complément à  deux)


  • AliGatorAliGator Membre, Modérateur


    Je travaille avec des index, de type NSUInteger.


    J'ai une @property NSInteger maxIndex. Elle vaut -1 s'il n'y a aucun index.

    C'est pour ça qu'il existe la constante NSNotFound. Et quand tu veux savoir si cet index correspond à  une valeur non trouvée tu compares avec un == ou un != NSNotFound et sûrement pas avec un > ou < du coup.


  • C'est pour ça qu'il existe la constante NSNotFound. Et quand tu veux savoir si cet index correspond à  une valeur non trouvée tu compares avec un == ou un != NSNotFound et sûrement pas avec un > ou < du coup.




    C'est classe ce truc !

  • AliGatorAliGator Membre, Modérateur

    C'est classe ce truc !

    C'est une constante utilisée un peu partout dans Cocoa du temps où les gens écrivaient encore de l'Objective-C et qu'on n'avait pas de moyen de rentre un entier "nil" (alors qu'en Swift c'est possible et ça rend le tout bien plus pratique et plus lisible :-P)

    Y'a pas mal d'API de Cocoa, écrites du temps d'Objective-C, qui utilisent cette valeur de retour.
    - Genre toutes les méthodes "rangeOfXXX" de NSString (rangeOfString:, rangeOfCharactersFromSet:, etc) retournent un NSRange qui indique l'emplacement en question si l'élément a été trouvé, et un NSRange dont la location vaut NSNotFound (et de longueur 0) s'il n'a pas été trouvé.
    - De même pour toutes les méthodes de NSArray du genre "indexOfObject:", qui retournent NSNotFound si l'élément n'a pas été trouvé.

    Donc ça date pas d'hier cet astuce, et c'est aussi pour ça que je te conseille d'utiliser cette même constante NSNotFound si tu veux en faire un usage similaire pour ton code avec maxIndex.

    Après, avec Swift et la possibilité d'avoir des "Int?" donc des entiers optionals qui peuvent être nil, c'est sûr qu'on a bien mieux maintenant, mais bon si t'es obligé de continuer à  faire de l'Objective-C en attendant faut se contenter de NSNotFound ;)
  • colas_colas_ Membre
    septembre 2015 modifié #13


     sûrement pas avec un > ou < du coup.




     


    Non, ça c'était pour recalculer le maxIndex !


     


     




    NSNotFound 




     


     


    Je connaissais NSNotFound. Mais, ici en tant que valeurMax invalide, je pense que -1 est plus logique :P (car NSNotFound est très grand !!)



    enum {NSNotFound = NSIntegerMax};

    Je trouve qu'évaluer (NSUInteger)0 > -1 à  NO, c'est quand même sioux ! Je comprends que c'est logique mais on aurait pu aussi imaginer qu'il fasse un cast automatique dans ce cas. Je le signalais car ça peut être à  l'origine de bugs sioux.


     


     


    Et oui, avec swift, c'est maintenant beaucoup mieux !!


    Est-ce qu'on ne pourrait pas (dans le cas par exemple où l'on a ≥2 valeurs spéciales et non entières) étendre Int (créer un sous-type de Int) et ajouter des symboles pour ces valeurs à  exclure ?


  • Je pense qu'il fait bien un cast automatique, mais de -1 pas de ta variable.


  • AliGatorAliGator Membre, Modérateur

    Je connaissais NSNotFound. Mais, ici en tant que valeurMax invalide, je pense que -1 est plus logique :P (car NSNotFound est très grand !!)

    Historiquement NSNotFound est égal à  ((NSUInteger)(-1)) (soit la valeur -1 castée en unsigned). Ce qui est la même valeur que NSIntegerMax d'ailleurs.

    Je trouve qu'évaluer (NSUInteger)0 > -1 à  NO, c'est quand même sioux ! Je comprends que c'est logique mais on aurait pu aussi imaginer qu'il fasse un cast automatique dans ce cas.

    Mais il FAIT un cast automatique justement ! Il cast implicitement ce "-1" en NSUInteger, ce qui l'évalue alors à  ((NSUInteger)(-1)) = NSIntegerMax, et c'est justement pour ça que c'est évalué à  NO.

    Je le signalais car ça peut être à  l'origine de bugs sioux.

    Tout à  fait, et c'est pour ça qu'il y a un warning. Je t'invite fortement à  activer ces warnings par défaut sur tous tes projets (c'est bien dommage qu'ils ne le soient pas par défaut " du moins pas avec les projets créés avec les anciennes versions de Xcode je sais pas si ça a changé depuis)

    1) Le warning "Sign Comparison" (GCC_WARN_SIGN_COMPARE, -Wsign-compare) est celui qui va te générer un warning si tu compares un NSUInteger avec un NSInteger, et t'aurais justement mis la puce à  l'oreille si tu l'avais activé sur ton code. Celui là  faut pas hésiter, je comprend même pas pourquoi il est pas à  YES dans les "iOS Defaults".
     
    2) Tu as aussi le warning "Implicit Signess Conversions" (CLANG_WARN_IMPLICIT_SIGN_CONVERSION) qui va quant à  lui te signaler si tu as des conversions implicites d'un signed vers un unsigned ou vice-versa, te forçant à  caster explicitement si c'est vraiment ce que tu veux.
    Lui il est un peu violent, car par exemple quand tu implémenter UITableViewDataSource et sa méthode numberOfRowsInSeciton, qui est sensé retourner un NSInteger (eh oui, il est pas Unsigned celui-là , j'ai jamais trop compris pourquoi, ça vient sans doute de NSIndexPath qui contient représente une suite index non signés et ne sert pas qu'à  UITableView), et du coup si tu "return myModelArray.count" il va te mettre un warning car NSArray.count est un NSUInteger, donc tu devras faire un cast explicite à  chaque fois... mais bon au moins ça te fait réaliser où sont faites les conversions et tu le feras en connaissance de cause.


    Est-ce qu'on ne pourrait pas (dans le cas par exemple où l'on a ≥2 valeurs spéciales et non entières) étendre Int (créer un sous-type de Int) et ajouter des symboles pour ces valeurs à  exclure ?

    Non en Swift si tu as plus d'une valeur spéciale (donc pas juste "une valeur ou bien nil", tu vas créer un enum avec associated value. D'ailleurs, un Optional n'est rien d'autre qu'un enum avec 2 cas possibles : un "case None" qui représente l'absence de valeur, et un "case Some(value)" qui représente la présence d'une valeur, avec ladite valeur alors associée. Du coup si tu as besoin de + d'un cas d'exception, tu vas faire un enum aux petits oignons avec des "case" qui seront nommés spécifiquement pour ton cas particulier et pourront représenter tous tes états possibles. Genre "case NilValue", "case UnknownValue", "case InvalidValue", "case Error(NSError)", et bien sûr le "case Value(Int)", pour représenter une absence de valeur, une valeur inconnue, une valeur invalide, un cas d'erreur lors de la récupération de la valeur, et le cas droit où t'as une véritable valeur de type Int.



  • 1) Le warning "Sign Comparison" (GCC_WARN_SIGN_COMPARE, -Wsign-compare) est celui qui va te générer un warning si tu compares un NSUInteger avec un NSInteger, et t'aurais justement mis la puce à  l'oreille si tu l'avais activé sur ton code. Celui là  faut pas hésiter, je comprend même pas pourquoi il est pas à  YES dans les "iOS Defaults".

     

    2) Tu as aussi le warning "Implicit Signess Conversions" (CLANG_WARN_IMPLICIT_SIGN_CONVERSION) qui va quant à  lui te signaler si tu as des conversions implicites d'un signed vers un unsigned ou vice-versa, te forçant à  caster explicitement si c'est vraiment ce que tu veux.
    Lui il est un peu violent, car par exemple quand tu implémenter UITableViewDataSource et sa méthode numberOfRowsInSeciton, qui est sensé retourner un NSInteger (eh oui, il est pas Unsigned celui-là , j'ai jamais trop compris pourquoi, ça vient sans doute de NSIndexPath qui contient représente une suite index non signés et ne sert pas qu'à  UITableView), et du coup si tu "return myModelArray.count" il va te mettre un warning car NSArray.count est un NSUInteger, donc tu devras faire un cast explicite à  chaque fois... mais bon au moins ça te fait réaliser où sont faites les conversions et tu le feras en connaissance de cause.

     




     


    Bon à  savoir !


    Merci !

Connectez-vous ou Inscrivez-vous pour répondre.