web-dev-qa-db-fra.com

Est-il sûr d'utiliser -1 pour mettre tous les bits à vrai?

J'ai vu ce modèle utilisé beaucoup en C & C++.

unsigned int flags = -1;  // all bits are true

Est-ce un bon moyen portable d'accomplir cela? Ou utilise 0xffffffff ou ~0 mieux?

130
hyperlogic

Je vous recommande de le faire exactement comme vous l'avez montré, car c'est le plus simple. Initialiser à -1 qui fonctionnera toujours , indépendamment de la représentation réelle du signe, tandis que ~ aura parfois un comportement surprenant car vous devrez avoir le bon type d'opérande. Ce n'est qu'alors que vous obtiendrez la valeur la plus élevée d'un type unsigned.

Pour un exemple de surprise possible, considérez celle-ci:

unsigned long a = ~0u;

Il ne stockera pas nécessairement un modèle avec tous les bits 1 dans a. Mais il créera d'abord un modèle avec tous les bits 1 dans un unsigned int, puis affectez-le à a. Ce qu'il se passe quand unsigned long a plus de bits, c'est que tous ne sont pas 1.

Et considérons celui-ci, qui échouera sur une représentation du complément à deux:

unsigned int a = ~0; // Should have done ~0u !

La raison en est que ~0 doit inverser tous les bits. Une inversion qui donnera -1 sur une machine à deux compléments (qui est la valeur dont nous avons besoin!), mais ne produira pas _ -1 sur une autre représentation. Sur la machine d'un complément, elle donne zéro. Ainsi, sur la machine du complément à un, ce qui précède initialise a à zéro.

Ce que vous devez comprendre, c'est qu'il s'agit de valeurs - pas de bits. La variable est initialisée avec une valeur . Si dans l'initialiseur vous modifiez les bits de la variable utilisée pour l'initialisation, la valeur sera générée en fonction de ces bits. La valeur dont vous avez besoin pour initialiser a à la valeur la plus élevée possible est -1 ou UINT_MAX. Le second dépendra du type de a - vous devrez utiliser ULONG_MAX pour un unsigned long. Cependant, le premier ne dépendra pas de son type, et c'est une belle façon d'obtenir la valeur la plus élevée.

Nous ne parlons pas de savoir si -1 a tous les bits un (il n'en a pas toujours). Et nous ne parlons pas de savoir si ~0 a tous les bits un (il en a bien sûr).

Mais ce dont nous parlons, c'est du résultat de la variable flags initialisée. Et pour cela, seulement -1 fonctionnera avec tous les types et machines.

152
  • unsigned int flags = -1; est portable.
  • unsigned int flags = ~0; n'est pas portable car il repose sur une représentation à deux compléments.
  • unsigned int flags = 0xffffffff; n'est pas portable car il suppose des entiers 32 bits.

Si vous souhaitez définir tous les bits d'une manière garantie par la norme C, utilisez le premier.

48
Dingo

Franchement, je pense que tous les fff sont plus lisibles. En ce qui concerne le commentaire selon lequel c'est un contre-modèle, si vous vous souciez vraiment que tous les bits sont définis/effacés, je dirais que vous êtes probablement dans une situation où vous vous souciez de la taille de la variable de toute façon, ce qui nécessiterait quelque chose comme boost :: uint16_t, etc.

25
Doug T.

Une façon d'éviter les problèmes mentionnés consiste simplement à:

unsigned int flags = 0;
flags = ~flags;

Portable et au point.

17
hammar

Je ne suis pas sûr que l'utilisation d'un entier non signé pour les drapeaux soit une bonne idée en premier lieu en C++. Qu'en est-il du bitset et similaires?

std::numeric_limit<unsigned int>::max() est mieux parce que 0xffffffff suppose que int non signé est un entier 32 bits.

13
Edouard A.
unsigned int flags = -1;  // all bits are true

"Est-ce un bon moyen [] portable d'accomplir cela?"

Portable? Oui.

Bien? Debatable, comme en témoigne toute la confusion montrée sur ce fil. Être suffisamment clair pour que vos collègues programmeurs puissent comprendre le code sans confusion devrait être l'une des dimensions que nous mesurons pour un bon code.

En outre, cette méthode est sujette à avertissements du compilateur. Pour éluder l'avertissement sans paralyser votre compilateur, vous auriez besoin d'un cast explicite. Par exemple,

unsigned int flags = static_cast<unsigned int>(-1);

La distribution explicite nécessite que vous fassiez attention au type cible. Si vous faites attention au type de cible, vous éviterez naturellement les pièges des autres approches.

Mon conseil serait de faire attention au type de cible et de s'assurer qu'il n'y a pas de conversions implicites. Par exemple:

unsigned int flags1 = UINT_MAX;
unsigned int flags2 = ~static_cast<unsigned int>(0);
unsigned long flags3 = ULONG_MAX;
unsigned long flags4 = ~static_cast<unsigned long>(0);

Tous sont corrects et plus évidents pour vos collègues programmeurs.

Et avec C++ 11: Nous pouvons utiliser auto pour rendre encore plus simple l'une d'entre elles:

auto flags1 = UINT_MAX;
auto flags2 = ~static_cast<unsigned int>(0);
auto flags3 = ULONG_MAX;
auto flags4 = ~static_cast<unsigned long>(0);

Je considère correct et évident mieux que simplement correct.

11
Adrian McCarthy

La conversion de -1 en n'importe quel type non signé est garantie par la norme pour aboutir à un tout. Utilisation de ~0U est généralement mauvais puisque 0 a le type unsigned int et ne remplira pas tous les bits d'un type non signé plus grand, sauf si vous écrivez explicitement quelque chose comme ~0ULL. Sur les systèmes sains, ~0 doit être identique à -1, mais comme la norme autorise les représentations de complément à un et de signe/amplitude, elle n'est pas à proprement parler portable.

Bien sûr, il est toujours correct d'écrire 0xffffffff si vous savez que vous avez besoin d'exactement 32 bits, mais -1 a l'avantage de fonctionner dans n'importe quel contexte même si vous ne connaissez pas la taille du type, comme les macros qui fonctionnent sur plusieurs types, ou si la taille de le type varie selon la mise en œuvre. Si vous connaissez le type, un autre moyen sûr d'obtenir tout-en-un est la limite des macros UINT_MAX, ULONG_MAX, ULLONG_MAX, etc.

Personnellement, j'utilise toujours -1. Cela fonctionne toujours et vous n'avez pas à y penser.

10
R..

Oui. Comme mentionné dans d'autres réponses, -1 est le plus portable; cependant, il n'est pas très sémantique et déclenche des avertissements du compilateur.

Pour résoudre ces problèmes, essayez cette aide simple:

static const struct All1s
{
    template<typename UnsignedType>
    inline operator UnsignedType(void) const
    {
        static_assert(std::is_unsigned<UnsignedType>::value, "This is designed only for unsigned types");
        return static_cast<UnsignedType>(-1);
    }
} ALL_BITS_TRUE;

Usage:

unsigned a = ALL_BITS_TRUE;
uint8_t  b = ALL_BITS_TRUE;
uint16_t c = ALL_BITS_TRUE;
uint32_t d = ALL_BITS_TRUE;
uint64_t e = ALL_BITS_TRUE;
5
Diamond Python

Tant que vous avez #include <limits.h> comme l'un de vos inclus, vous devez simplement utiliser

unsigned int flags = UINT_MAX;

Si vous voulez une longue quantité de bits, vous pouvez utiliser

unsigned long flags = ULONG_MAX;

Ces valeurs sont garanties d'avoir tous les bits de valeur du résultat mis à 1, quelle que soit la façon dont les entiers signés sont implémentés.

5
Michael Norrish

Je ne ferais pas la chose -1. C'est plutôt non intuitif (du moins pour moi). L'affectation de données signées à une variable non signée semble simplement être une violation de l'ordre naturel des choses.

Dans votre situation, j'utilise toujours 0xFFFF. (Utilisez le bon nombre de F pour la taille variable bien sûr.)

[BTW, je vois très rarement l'astuce -1 effectuée dans le code du monde réel.]

De plus, si vous vous souciez vraiment des bits individuels dans une variable, ce serait une bonne idée de commencer à utiliser la largeur fixe uint8_t, uint16_t, uint32_t les types.

3
myron-semack

Sur les processeurs Intel IA-32, il est correct d'écrire 0xFFFFFFFF dans un registre 64 bits et d'obtenir les résultats attendus. En effet, IA32e (l'extension 64 bits vers IA32) ne prend en charge que les intermédiaires 32 bits. Dans les instructions 64 bits, les intermédiaires 32 bits sont extension de signe à 64 bits.

Ce qui suit est illégal:

mov rax, 0ffffffffffffffffh

Ce qui suit met 64 1s dans RAX:

mov rax, 0ffffffffh

Juste pour être complet, ce qui suit met 32 ​​1 dans la partie inférieure de RAX (aka EAX):

mov eax, 0ffffffffh

Et en fait, j'ai eu des programmes qui échouaient quand j'ai voulu écrire 0xffffffff dans une variable 64 bits et j'ai plutôt eu 0xffffffffffffffffff. En C, ce serait:

uint64_t x;
x = UINT64_C(0xffffffff)
printf("x is %"PRIx64"\n", x);

le résultat est:

x is 0xffffffffffffffff

J'ai pensé à poster ceci comme un commentaire à toutes les réponses qui disaient que 0xFFFFFFFF suppose 32 bits, mais tant de gens y ont répondu que je pensais que je l'ajouterais comme réponse distincte.

2
Nathan Fellman

Bien que le 0xFFFF (ou 0xFFFFFFFF, etc.) peut être plus facile à lire, il peut briser la portabilité du code qui serait autrement portable. Considérez, par exemple, une routine de bibliothèque pour compter combien d'éléments dans une structure de données ont certains bits définis (les bits exacts étant spécifiés par l'appelant). La routine peut être totalement agnostique quant à ce que représentent les bits, mais doit toujours avoir une constante "tous les bits définis". Dans un tel cas, -1 sera bien meilleur qu'une constante hexadécimale car il fonctionnera avec n'importe quelle taille de bit.

L'autre possibilité, si une valeur typedef est utilisée pour le masque binaire, serait d'utiliser ~ (bitMaskType) 0; s'il se trouve que le masque de bits n'est que de type 16 bits, cette expression n'aura que 16 bits définis (même si 'int' serait autrement de 32 bits) mais puisque 16 bits seront tout ce qui est requis, les choses devraient bien se passer fourni que l'on utilise réellement le type approprié dans le transtypage.

Par ailleurs, les expressions de la forme longvar &= ~[hex_constant] ont un méchant gotcha si la constante hexadécimale est trop grande pour tenir dans un int, mais peut tenir dans un unsigned int. Si un int fait 16 bits, alors longvar &= ~0x4000; ou longvar &= ~0x10000; effacera un bit de longvar, mais longvar &= ~0x8000; effacera le bit 15 et tous les bits au-dessus. Les valeurs qui correspondent à int verront l'opérateur complément appliqué à un type int, mais le résultat sera étendu à long, en définissant les bits supérieurs. Valeurs trop grandes pour unsigned int aura l'opérateur complémentaire appliqué au type long. Cependant, les valeurs comprises entre ces tailles appliqueront l'opérateur complément au type unsigned int, qui sera ensuite converti en type long sans extension de signe.

2
supercat

Voir la réponse de litb pour une explication très claire des problèmes.

Mon désaccord est que, à strictement parler, il n'y a aucune garantie pour les deux cas. Je ne connais aucune architecture qui ne représente pas une valeur non signée de "un de moins de deux à la puissance du nombre de bits" comme tous les bits définis, mais voici ce que la norme dit réellement (3.9.1/7 plus note 44):

Les représentations des types intégraux doivent définir des valeurs en utilisant un système de numérotation binaire pur. [Note 44:] Une représentation positionnelle pour les nombres entiers qui utilise les chiffres binaires 0 et 1, dans laquelle les valeurs représentées par les bits successifs sont additives, commencent par 1 et sont multipliées par la puissance intégrale successive de 2, sauf peut-être pour le bit avec la position la plus élevée.

Cela laisse la possibilité pour l'un des bits d'être n'importe quoi.

2
James Hopkin

Pratiquement: oui

Théoriquement: Non.

-1 = 0xFFFFFFFF (ou quelle que soit la taille d'un int sur votre plateforme) n'est vrai qu'avec l'arithmétique du complément à deux. En pratique, cela fonctionnera, mais il existe des machines existantes (ordinateurs centraux IBM, etc.) où vous avez un bit de signe réel plutôt qu'une représentation de complément à deux. Votre solution ~ 0 proposée devrait fonctionner partout.

1
Drew Hall

Comme d'autres l'ont mentionné, -1 est la bonne façon de créer un entier qui se convertira en un type non signé avec tous les bits définis sur 1. Cependant, la chose la plus importante en C++ utilise des types corrects. Par conséquent, la bonne réponse à votre problème (qui comprend la réponse à la question que vous avez posée) est la suivante:

std::bitset<32> const flags(-1);

Cela contiendra toujours la quantité exacte de bits dont vous avez besoin. Il construit un std::bitset avec tous les bits mis à 1 pour les mêmes raisons mentionnées dans d'autres réponses.

1
David Stone

Une façon de rendre le sens un peu plus évident tout en évitant de répéter le type:

const auto flags = static_cast<unsigned int>(-1);
0
FlorianH

C'est certainement sûr, car -1 aura toujours tous les bits disponibles, mais j'aime mieux ~ 0. -1 n'a tout simplement pas beaucoup de sens pour un unsigned int. 0xFF... n'est pas bon car cela dépend de la largeur du type.

0
Zifre

Tirer parti du fait que l'attribution de tous les bits à un pour un type non signé équivaut à prendre la valeur maximale possible pour le type donné,
et étendant la portée de la question à tous types entiers non signés :

L'affectation de -1 fonctionne pour tout type d'entier non signé (entier non signé, uint8_t, uint16_t, etc.) pour C et C++ .

Comme alternative, pour C++, vous pouvez soit:

  1. Incluez <limits> Et utilisez std::numeric_limits< your_type >::max()
  2. Écrivez un fonction de modèle personnalisé (Cela permettrait également un certain contrôle d'intégrité, c'est-à-dire si le type de destination est vraiment un type non signé)

Le but pourrait être d'ajouter plus de clarté, car l'attribution de -1 Nécessiterait toujours des commentaires explicatifs.

0
Antonio

Je dis:

int x;
memset(&x, 0xFF, sizeof(int));

Cela vous donnera toujours le résultat souhaité.

0
Alex