c++ créer - Comment définir, effacer et basculer un seul bit?





écrire fichiers (22)


Il est parfois utile d'utiliser une enum pour nommer les bits:

enum ThingFlags = {
  ThingMask  = 0x0000,
  ThingFlag0 = 1 << 0,
  ThingFlag1 = 1 << 1,
  ThingError = 1 << 8,
}

Ensuite, utilisez les noms plus tard. C'est-à-dire écrire

thingstate |= ThingFlag1;
thingstate &= ~ThingFlag0;
if (thing & ThingError) {...}

régler, effacer et tester. De cette façon, vous masquez les nombres magiques du reste de votre code.

Autre que cela, j'approuve la solution de Jeremy.

Comment définir, effacer et basculer un peu en C / C ++?




Comme cela est étiqueté "incorporé", je suppose que vous utilisez un microcontrôleur. Toutes les suggestions ci-dessus sont valables et fonctionnent (lecture-modification-écriture, unions, structures, etc.).

Cependant, lors d’un épisode de débogage basé sur un oscilloscope, j’ai été étonné de constater que ces méthodes entraînaient une surcharge considérable en cycles de processeur, par rapport à l’écriture d’une valeur directement dans les registres PORTnSET / PORTnCLEAR du micro, ce qui fait une réelle différence lorsqu'il existe des boucles serrées / hautes. broches à bascule ISR.

Pour ceux qui ne sont pas familiers: dans mon exemple, le micro a un registre général d’états de broches PORTn qui reflète les broches de sortie; ainsi, PORTn | = BIT_TO_SET entraîne une lecture-modification-écriture sur ce registre. Cependant, les registres PORTnSET / PORTnCLEAR prennent un 1 pour signifier "veuillez faire ce bit 1" (SET) ou "veuillez faire ce bit zéro" (CLEAR) et un "0" pour signifier "laissez la broche isolée". vous vous retrouvez donc avec deux adresses de port selon que vous définissez ou effacez le bit (pas toujours pratique) mais une réaction beaucoup plus rapide et un code assemblé plus petit.




Vérifiez un bit à un emplacement arbitraire dans une variable de type arbitraire:

#define bit_test(x, y)  ( ( ((const char*)&(x))[(y)>>3] & 0x80 >> ((y)&0x07)) >> (7-((y)&0x07) ) )

Exemple d'utilisation:

int main(void)
{
    unsigned char arr[8] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };

    for (int ix = 0; ix < 64; ++ix)
        printf("bit %d is %d\n", ix, bit_test(arr, ix));

    return 0;
}

Notes: Ceci est conçu pour être rapide (étant donné sa flexibilité) et non branchy. Il en résulte un code machine SPARC efficace lors de la compilation de Sun Studio 8; Je l'ai également testé avec MSVC ++ 2008 sur amd64. Il est possible de créer des macros similaires pour définir et effacer des bits. La principale différence entre cette solution et de nombreuses autres solutions réside dans le fait qu’elle fonctionne pour n’importe quel emplacement dans pratiquement tout type de variable.




De snip-c.zip de snip-c.zip:

/*
**  Bit set, clear, and test operations
**
**  public domain snippet by Bob Stout
*/

typedef enum {ERROR = -1, FALSE, TRUE} LOGICAL;

#define BOOL(x) (!(!(x)))

#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))

OK, analysons les choses ...

L’expression courante avec laquelle vous semblez avoir des problèmes est "(1L << (posn))". Tout cela ne fait que créer un masque avec un seul bit et qui fonctionnera avec n'importe quel type entier. L'argument "posn" spécifie la position où vous voulez le bit. Si posn == 0, alors cette expression aura pour résultat:

    0000 0000 0000 0000 0000 0000 0000 0001 binary.

Si posn == 8, il sera évalué à

    0000 0000 0000 0000 0000 0001 0000 0000 binary.

En d'autres termes, il crée simplement un champ de 0 avec un 1 à la position spécifiée. La seule partie délicate est dans la macro BitClr () où nous devons définir un seul bit 0 dans un champ de 1. Ceci est accompli en utilisant le complément à 1 de la même expression, comme indiqué par l'opérateur tilde (~).

Une fois le masque créé, il est appliqué à l'argument comme vous le suggérez, à l'aide des opérateurs bitwise et (&), ou (|), et xor (^). Comme le masque est de type long, les macros fonctionneront tout aussi bien sur les caractères, courts, int ou longs.

L'essentiel est qu'il s'agit d'une solution générale à toute une classe de problèmes. Il est bien sûr possible et même approprié de réécrire l'équivalent de l'une de ces macros avec des valeurs de masque explicites chaque fois que vous en avez besoin, mais pourquoi le faire? N'oubliez pas que la substitution de macros se produit dans le préprocesseur et que le code généré reflète donc le fait que les valeurs sont considérées comme constantes par le compilateur. Autrement dit, il est tout aussi efficace d'utiliser les macros généralisées que de "réinventer la roue" chaque fois que vous en avez besoin. faire un peu de manipulation.

Pas convaincu? Voici un code de test - j'ai utilisé Watcom C avec une optimisation complète et sans utiliser _cdecl afin que le désassemblage résultant soit aussi propre que possible:

---- [TEST.C] ----------------------------------------- -----------------------

#define BOOL(x) (!(!(x)))

#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))

int bitmanip(int word)
{
      word = BitSet(word, 2);
      word = BitSet(word, 7);
      word = BitClr(word, 3);
      word = BitFlp(word, 9);
      return word;
}

---- [TEST.OUT (démonté)] ------------------------------------------ ---------

Module: C:\BINK\tst.c
Group: 'DGROUP' CONST,CONST2,_DATA,_BSS

Segment: _TEXT  BYTE   00000008 bytes  
 0000  0c 84             bitmanip_       or      al,84H    ; set bits 2 and 7
 0002  80 f4 02                          xor     ah,02H    ; flip bit 9 of EAX (bit 1 of AH)
 0005  24 f7                             and     al,0f7H
 0007  c3                                ret     

No disassembly errors

---- [finis] ------------------------------------------- ----------------------




Si vous faites beaucoup de bricolage, vous voudrez peut-être utiliser des masques pour que tout soit plus rapide. Les fonctions suivantes sont très rapides et restent flexibles (elles permettent le twiddling en bitmap de n’importe quelle taille).

const unsigned char TQuickByteMask[8] =
{
   0x01, 0x02, 0x04, 0x08,
   0x10, 0x20, 0x40, 0x80,
};


/** Set bit in any sized bit mask.
 *
 * @return    none
 *
 * @param     bit    - Bit number.
 * @param     bitmap - Pointer to bitmap.
 */
void TSetBit( short bit, unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;        // Index to byte.
    n = bit % 8;        // Specific bit in byte.

    bitmap[x] |= TQuickByteMask[n];        // Set bit.
}


/** Reset bit in any sized mask.
 *
 * @return  None
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
void TResetBit( short bit, unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;        // Index to byte.
    n = bit % 8;        // Specific bit in byte.

    bitmap[x] &= (~TQuickByteMask[n]);    // Reset bit.
}


/** Toggle bit in any sized bit mask.
 *
 * @return   none
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
void TToggleBit( short bit, unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;        // Index to byte.
    n = bit % 8;        // Specific bit in byte.

    bitmap[x] ^= TQuickByteMask[n];        // Toggle bit.
}


/** Checks specified bit.
 *
 * @return  1 if bit set else 0.
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
short TIsBitSet( short bit, const unsigned char *bitmap)
{
    short n, x;

    x = bit / 8;    // Index to byte.
    n = bit % 8;    // Specific bit in byte.

    // Test bit (logigal AND).
    if (bitmap[x] & TQuickByteMask[n])
        return 1;

    return 0;
}


/** Checks specified bit.
 *
 * @return  1 if bit reset else 0.
 *
 * @param   bit    - Bit number.
 * @param   bitmap - Pointer to bitmap.
 */
short TIsBitReset( short bit, const unsigned char *bitmap)
{
    return TIsBitSet(bit, bitmap) ^ 1;
}


/** Count number of bits set in a bitmap.
 *
 * @return   Number of bits set.
 *
 * @param    bitmap - Pointer to bitmap.
 * @param    size   - Bitmap size (in bits).
 *
 * @note    Not very efficient in terms of execution speed. If you are doing
 *        some computationally intense stuff you may need a more complex
 *        implementation which would be faster (especially for big bitmaps).
 *        See (http://graphics.stanford.edu/~seander/bithacks.html).
 */
int TCountBits( const unsigned char *bitmap, int size)
{
    int i, count = 0;

    for (i=0; i<size; i++)
        if (TIsBitSet(i, bitmap))
            count++;

    return count;
}

Remarque: pour définir le bit 'n' dans un entier de 16 bits, procédez comme suit:

TSetBit( n, &my_int);

C'est à vous de vous assurer que le numéro de bit se trouve dans la plage de la bitmap que vous transmettez. Notez que pour les processeurs little endian, les octets, les mots, les dwords, les qwords, etc., se mappent correctement les uns dans les autres en mémoire sur...).




L’approche bitfield présente d’autres avantages dans l’arène embarquée. Vous pouvez définir une structure qui mappe directement sur les bits d'un registre matériel particulier.

struct HwRegister {
    unsigned int errorFlag:1;  // one-bit flag field
    unsigned int Mode:3;       // three-bit mode field
    unsigned int StatusCode:4;  // four-bit status code
};

struct HwRegister CR3342_AReg;

Vous devez être au courant de l'ordre d'emballage - je pense que c'est d'abord MSB, mais cela peut dépendre de l'implémentation. Vérifiez également comment votre compilateur gère les champs traversant des limites d’octets.

Vous pouvez ensuite lire, écrire et tester les valeurs individuelles comme auparavant.




Ce programme doit changer n'importe quel bit de données de 0 à 1 ou de 1 à 0:

{
    unsigned int data = 0x000000F0;
    int bitpos = 4;
    int bitvalue = 1;
    unsigned int bit = data;
    bit = (bit>>bitpos)&0x00000001;
    int invbitvalue = 0x00000001&(~bitvalue);
    printf("%x\n",bit);

    if (bitvalue == 0)
    {
        if (bit == 0)
            printf("%x\n", data);
        else
        {
             data = (data^(invbitvalue<<bitpos));
             printf("%x\n", data);
        }
    }
    else
    {
        if (bit == 1)
            printf("elseif %x\n", data);
        else
        {
            data = (data|(bitvalue<<bitpos));
            printf("else %x\n", data);
        }
    }
}



Une version basée sur un modèle C ++ 11 (mise dans un en-tête):

namespace bit {
    template <typename T1, typename T2> inline void set  (T1 &variable, T2 bit) {variable |=  ((T1)1 << bit);}
    template <typename T1, typename T2> inline void clear(T1 &variable, T2 bit) {variable &= ~((T1)1 << bit);}
    template <typename T1, typename T2> inline void flip (T1 &variable, T2 bit) {variable ^=  ((T1)1 << bit);}
    template <typename T1, typename T2> inline bool test (T1 &variable, T2 bit) {return variable & ((T1)1 << bit);}
}

namespace bitmask {
    template <typename T1, typename T2> inline void set  (T1 &variable, T2 bits) {variable |= bits;}
    template <typename T1, typename T2> inline void clear(T1 &variable, T2 bits) {variable &= ~bits;}
    template <typename T1, typename T2> inline void flip (T1 &variable, T2 bits) {variable ^= bits;}
    template <typename T1, typename T2> inline bool test_all(T1 &variable, T2 bits) {return ((variable & bits) == bits);}
    template <typename T1, typename T2> inline bool test_any(T1 &variable, T2 bits) {return variable & bits;}
}



Mettre un peu

Utilisez l'opérateur OU au niveau du bit ( | ) pour définir un bit.

number |= 1UL << n;

Cela définira le nième bit du number . n doit être égal à zéro si vous souhaitez définir le 1 er bit et ainsi de suite jusqu'à n-1 , si vous souhaitez définir le n ème bit.

Utilisez 1ULL si le number est plus large que unsigned long ; La promotion de 1UL << n ne se produit qu'après l'évaluation de 1UL << n où son comportement indéfini se déplace de plus de la largeur d'un long . La même chose s'applique à tous les autres exemples.

Effacer un peu

Utilisez l'opérateur AND au niveau du bit ( & ) pour effacer un bit.

number &= ~(1UL << n);

Cela effacera le nième bit du number . Vous devez inverser la chaîne de bits avec l'opérateur binaire NOT ( ~ ), puis AND.

Basculer un peu

L'opérateur XOR ( ^ ) peut être utilisé pour basculer un bit.

number ^= 1UL << n;

Cela va basculer le nième bit du number .

Vérifier un peu

Vous n'avez pas demandé cela, mais je pourrais aussi bien l'ajouter.

Pour vérifier un bit, déplacez le nombre n vers la droite, puis bitwise ET cela:

bit = (number >> n) & 1U;

Cela mettra la valeur du nième bit du number dans le bit variable.

Changer le nième bit en x

La définition du nième bit sur 1 ou sur 0 peut être réalisée avec les éléments suivants sur une implémentation C ++ du complément à 2:

number ^= (-x ^ number) & (1UL << n);

Le bit n sera défini si x est 1 et effacé si x est 0 . Si x a une autre valeur, vous obtenez des déchets. x = !!x le booleanise à 0 ou 1.

Pour rendre cela indépendant du comportement de négation du complément à 2 (où -1 a tous les bits définis, contrairement à l'implémentation C ++ du complément à 1 ou du signe / magnitude), utilisez la négation non signée.

number ^= (-(unsigned long)x ^ number) & (1UL << n);

ou

unsigned long newbit = !!x;    // Also booleanize to force 0 or 1
number ^= (-newbit ^ number) & (1UL << n);

C'est généralement une bonne idée d'utiliser des types non signés pour la manipulation de bits portable.

De manière générale, il est également conseillé de ne pas copier / coller le code. De nombreuses personnes utilisent des macros de préprocesseur (comme la réponse du wiki de la communauté plus bas ) ou une sorte d'encapsulation.




Pour le débutant, je voudrais expliquer un peu plus avec un exemple:

Exemple:

value is 0x55;
bitnum : 3rd.

L'opérateur & est utilisé vérifier le bit:

0101 0101
&
0000 1000
___________
0000 0000 (mean 0: False). It will work fine if the third bit is 1 (then the answer will be True)

Basculer ou basculer:

0101 0101
^
0000 1000
___________
0101 1101 (Flip the third bit without affecting other bits)

| opérateur: définir le bit

0101 0101
|
0000 1000
___________
0101 1101 (set the third bit without affecting other bits)



Essayez l’une de ces fonctions en langage C pour changer le bit n:

char bitfield;

// Start at 0th position

void chang_n_bit(int n, int value)
{
    bitfield = (bitfield | (1 << n)) & (~( (1 << n) ^ (value << n) ));
}

Ou

void chang_n_bit(int n, int value)
{
    bitfield = (bitfield | (1 << n)) & ((value << n) | ((~0) ^ (1 << n)));
}

Ou

void chang_n_bit(int n, int value)
{
    if(value)
        bitfield |= 1 << n;
    else
        bitfield &= ~0 ^ (1 << n);
}

char get_n_bit(int n)
{
    return (bitfield & (1 << n)) ? 1 : 0;
}



Utilisez les opérateurs au niveau des bits: & |

Pour définir le dernier bit en 000b :

foo = foo | 001b

Pour vérifier le dernier bit de foo :

if ( foo & 001b ) ....

Pour effacer le dernier bit de foo :

foo = foo & 110b

J'ai utilisé XXXb pour plus de clarté. Vous travaillerez probablement avec une représentation HEX, en fonction de la structure de données dans laquelle vous stockez des bits.




Développer la bitsetréponse:

#include <iostream>
#include <bitset>
#include <string>

using namespace std;
int main() {
  bitset<8> byte(std::string("10010011");

  // Set Bit
  byte.set(3); // 10010111

  // Clear Bit
  byte.reset(2); // 10010101

  // Toggle Bit
  byte.flip(7); // 00010101

  cout << byte << endl;

  return 0;
}



Utilisez ceci:

int ToggleNthBit ( unsigned char n, int num )
{
    if(num & (1 << n))
        num &= ~(1 << n);
    else
        num |= (1 << n);

    return num;
}



Plus généralement, pour les bitmaps de taille arbitraire:

#define BITS 8
#define BIT_SET(  p, n) (p[(n)/BITS] |=  (0x80>>((n)%BITS)))
#define BIT_CLEAR(p, n) (p[(n)/BITS] &= ~(0x80>>((n)%BITS)))
#define BIT_ISSET(p, n) (p[(n)/BITS] &   (0x80>>((n)%BITS)))



J'utilise des macros définies dans un fichier d'en-tête pour gérer les bits définis et effacer:

/* a=target variable, b=bit number to act upon 0-n */
#define BIT_SET(a,b) ((a) |= (1ULL<<(b)))
#define BIT_CLEAR(a,b) ((a) &= ~(1ULL<<(b)))
#define BIT_FLIP(a,b) ((a) ^= (1ULL<<(b)))
#define BIT_CHECK(a,b) (!!((a) & (1ULL<<(b))))        // '!!' to make sure this returns 0 or 1

/* x=target variable, y=mask */
#define BITMASK_SET(x,y) ((x) |= (y))
#define BITMASK_CLEAR(x,y) ((x) &= (~(y)))
#define BITMASK_FLIP(x,y) ((x) ^= (y))
#define BITMASK_CHECK_ALL(x,y) (((x) & (y)) == (y))   // warning: evaluates y twice
#define BITMASK_CHECK_ANY(x,y) ((x) & (y))



L'autre option consiste à utiliser des champs de bits:

struct bits {
    unsigned int a:1;
    unsigned int b:1;
    unsigned int c:1;
};

struct bits mybits;

définit un champ de 3 bits (en réalité, il s'agit de trois champs de 1 bit). Les opérations sur les bits deviennent maintenant un peu plus simples (haha):

Pour définir ou effacer un peu:

mybits.b = 1;
mybits.c = 0;

Pour basculer un peu:

mybits.a = !mybits.a;
mybits.b = ~mybits.b;
mybits.c ^= 1;  /* all work */

Vérification un peu:

if (mybits.c)  //if mybits.c is non zero the next line below will execute

Cela ne fonctionne qu'avec des champs de bits de taille fixe. Sinon, vous devez recourir aux techniques de bricolage décrites dans les messages précédents.




Variable utilisée

int value, pos;

value - Data
pos - position du bit que nous sommes intéressés à définir, effacer ou basculer
Définir un bit

value = value | 1 << pos;

Effacer un peu

value = value & ~(1 << pos); 

Basculer un peu

value = value ^ 1 << pos;



Comment définissez-vous, effacez-vous et basculez-vous un seul bit?

Pour résoudre un problème de codage courant lors de la tentative de formation du masque:
1n’est pas toujours suffisamment large

Quels problèmes se produisent quand numberest un type plus large que 1?
xpeut être trop important pour le décalage 1 << xmenant à un comportement indéfini (UB). Même si ce xn'est pas trop grand, ~peut ne pas retourner assez de bits les plus significatifs.

// assume 32 bit int/unsigned
unsigned long long number = foo();

unsigned x = 40; 
number |= (1 << x);  // UB
number ^= (1 << x);  // UB
number &= ~(1 << x); // UB

x = 10;
number &= ~(1 << x); // Wrong mask, not wide enough

Pour assurer 1 est assez large:

Le code peut être utilisé de manière 1ullpédante ou intuitive (uintmax_t)1et permettre au compilateur d’optimiser.

number |= (1ull << x);
number |= ((uintmax_t)1 << x);

Ou casting - qui permet de résoudre les problèmes de codage / révision / maintenance en maintenant le casting correct et à jour.

number |= (type_of_number)1 << x;

Ou promouvez doucement 1en forçant une opération mathématique aussi large que le type de number.

number |= (number*0 + 1) << x;

Comme avec la plupart des manipulations de bits, mieux travailler avec non signés types plutôt que signés les




Utilisez l'un des opérateurs tels que définis here .

Pour définir un bit, utilisé int x = x | 0x?;?est la position du bit sous forme binaire.




Utilisation de la bibliothèque C ++ standard: std::bitset<N> .

Ou la version Boost : boost::dynamic_bitset .

Il n'est pas nécessaire de rouler le vôtre:

#include <bitset>
#include <iostream>

int main()
{
    std::bitset<5> x;

    x[1] = 1;
    x[2] = 0;
    // Note x[0-4]  valid

    std::cout << x << std::endl;
}
[Alpha:] > ./a.out
00010

La version Boost permet d'utiliser un jeu de bits de la taille d'un environnement d'exécution par rapport à un jeu de bits de la taille d'une bibliothèque standard .




Cette réponse est plus comme une addition et une légère modification aux réponses ci-dessus.

Dans certaines versions de Visual Studio (et éventuellement d'autres compilateurs), il y a un bug qui est vraiment ennuyeux et qui n'a pas de sens. Donc, si vous déclarez / définissez votre fonction d' swap comme ceci:

friend void swap(A& first, A& second) {

    std::swap(first.size, second.size);
    std::swap(first.arr, second.arr);

}

... le compilateur vous criera quand vous appelez la fonction d' swap :

Cela a quelque chose à voir avec l'appel d'une fonction friend et la transmission de this objet en paramètre.

Un moyen de contourner cela est de ne pas utiliser le mot-clé friend et redéfinir la fonction d' swap :

void swap(A& other) {

    std::swap(size, other.size);
    std::swap(arr, other.arr);

}

Cette fois, vous pouvez simplement appeler swap et passer à other chose, ce qui rend le compilateur heureux:

Après tout, vous n'avez pas besoin d'utiliser une fonction friend pour échanger 2 objets. Il est tout aussi logique de faire swap une fonction membre qui a un other objet en paramètre.

Vous avez déjà accès à this objet, donc le transmettre en tant que paramètre est techniquement redondant.





c++ c bit-manipulation bitwise-operators