c++ manipulation - Wie können Sie ein einzelnes Bit setzen, löschen und umschalten?




bitmaskierung multiple (22)

Wie können Sie ein wenig in C / C ++ einstellen, löschen und umschalten?


Answers

Die bitsetAntwort erweitern:

#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;
}

Wenn Sie diese gesamte Operation mit C-Programmierung im Linux-Kernel durchführen möchten, empfehle ich die Verwendung von Standard-APIs des Linux-Kernels.

Siehe https://www.kernel.org/doc/htmldocs/kernel-api/ch02s03.html

set_bit  Atomically set a bit in memory
clear_bit  Clears a bit in memory
change_bit  Toggle a bit in memory
test_and_set_bit  Set a bit and return its old value
test_and_clear_bit  Clear a bit and return its old value
test_and_change_bit  Change a bit and return its old value
test_bit  Determine whether a bit is set

Hinweis: Hier erfolgt die gesamte Operation in einem einzigen Schritt. Daher sind diese Eigenschaften auch auf SMP-Computern garantiert atomar und sorgen für die Kohärenz zwischen den Prozessoren.


int set_nth_bit(int num, int n){

    return (num | 1 << n);
}

int clear_nth_bit(int num, int n){

    return (num & ~( 1 << n));
}

int toggle_nth_bit(int num, int n){

    return num ^ (1 << n);
}

int check_nth_bit(int num, int n){

    return num & (1 << n);
}

Hier ist mein beliebtes Bit-Arithmetik-Makro, das für alle Arten von vorzeichenlosen Integer-Arrays von unsigned char bis zu size_t funktioniert (der größte Typ, mit dem man effizient arbeiten kann):

#define BITOP(a,b,op) \
 ((a)[(size_t)(b)/(8*sizeof *(a))] op ((size_t)1<<((size_t)(b)%(8*sizeof *(a)))))

Um ein bisschen zu setzen:

BITOP(array, bit, |=);

Um etwas zu löschen:

BITOP(array, bit, &=~);

Um ein wenig zu wechseln:

BITOP(array, bit, ^=);

Um ein bisschen zu testen:

if (BITOP(array, bit, &)) ...

usw.


Wenn Sie viel herumspielen, möchten Sie vielleicht Masken verwenden, die das Ganze schneller machen. Die folgenden Funktionen sind sehr schnell und dennoch flexibel (sie ermöglichen Bit-Twiddling in Bitmaps beliebiger Größe).

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;
}

Um das Bit 'n' in einer 16-Bit-Ganzzahl zu setzen, führen Sie die folgenden Schritte aus:

TSetBit( n, &my_int);

Sie müssen sicherstellen, dass sich die Bit-Nummer innerhalb des Bereichs der Bitmap befindet, die Sie übergeben. Beachten Sie, dass für kleine Endian-Prozessoren, die Bytes, Wörter, Dwords, Qwords usw. im Speicher richtig zugeordnet werden (der Hauptgrund dafür, dass Little-Endian-Prozessoren "besser" sind als Big-Endian-Prozessoren) auf...).


Variable verwendet

int value, pos;

value -
Datenposition - Position des Bits, das wir setzen, löschen oder umschalten
möchten Bit setzen

value = value | 1 << pos;

Klar ein bisschen

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

Ein wenig umschalten

value = value ^ 1 << pos;

Aus snip-c.zip -c.zips bitops.h:

/*
**  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, lass uns die Dinge analysieren ...

Der häufigste Ausdruck, mit dem Sie Probleme zu haben scheinen, lautet "(1L << (Posn))". Dazu erstellen Sie eine Maske mit einem einzigen Bit, die mit jedem Integer-Typ funktioniert. Das Argument "posn" gibt die Position an, an der Sie das Bit haben möchten. Wenn posn == 0 ist, wird dieser Ausdruck folgendermaßen ausgewertet:

    0000 0000 0000 0000 0000 0000 0000 0001 binary.

Wenn posn == 8, wird es zu ausgewertet

    0000 0000 0000 0000 0000 0001 0000 0000 binary.

Mit anderen Worten, es wird einfach ein Feld von 0 mit einer 1 an der angegebenen Position erstellt. Der einzige knifflige Teil ist das BitClr () -Makro, bei dem wir ein einzelnes 0-Bit in einem Feld von 1 setzen müssen. Dies wird erreicht, indem das 1er-Komplement des gleichen Ausdrucks verwendet wird, der vom Tilde (~) -Operator angegeben wird.

Nachdem die Maske erstellt wurde, wird sie auf das Argument genauso angewendet, wie Sie es vorgeschlagen haben, indem Sie die Operatoren bitweise und (&) oder (|) und xor (^) verwenden. Da die Maske vom Typ long ist, funktionieren die Makros genauso gut bei Zeichen, kurzen, int's oder longs.

Unter dem Strich ist dies eine allgemeine Lösung für eine ganze Klasse von Problemen. Es ist natürlich möglich und sogar angebracht, das Äquivalent eines dieser Makros jedes Mal mit expliziten Maskenwerten umzuschreiben, wenn Sie einen benötigen, aber warum? Denken Sie daran, dass die Makrosubstitution im Präprozessor erfolgt, sodass der generierte Code die Tatsache widerspiegelt, dass die Werte vom Compiler als konstant betrachtet werden. Das heißt, es ist genauso effizient, die verallgemeinerten Makros zu verwenden, als das Rad jedes Mal neu zu "erfinden" etwas manipulieren.

Nicht überzeugt? Hier ist ein Testcode - ich habe Watcom C mit voller Optimierung und ohne Verwendung von _cdecl verwendet, damit die resultierende Demontage so sauber wie möglich ist:

---- [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 (zerlegt)] ---------------------------------- ---------

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] --------------------------------------- ----------------------


Allgemeiner für Bitmaps beliebiger Größe:

#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)))

Manchmal ist es enum , die Bits mit einem enum zu benennen :

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

Verwenden Sie die Namen dann später. Ich schreibe

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

einstellen, löschen und testen. Auf diese Weise verstecken Sie die magischen Zahlen vor dem Rest Ihres Codes.

Abgesehen davon stimme ich Jeremys Lösung zu.


Hier sind einige Makros, die ich verwende:

SET_FLAG(Status, Flag)            ((Status) |= (Flag))
CLEAR_FLAG(Status, Flag)          ((Status) &= ~(Flag))
INVALID_FLAGS(ulFlags, ulAllowed) ((ulFlags) & ~(ulAllowed))
TEST_FLAGS(t,ulMask, ulBit)       (((t)&(ulMask)) == (ulBit))
IS_FLAG_SET(t,ulMask)             TEST_FLAGS(t,ulMask,ulMask)
IS_FLAG_CLEAR(t,ulMask)           TEST_FLAGS(t,ulMask,0)

Der Bitfield-Ansatz hat weitere Vorteile in der Embedded-Arena. Sie können eine Struktur definieren, die direkt auf die Bits in einem bestimmten Hardware-Register abgebildet wird.

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;

Sie müssen sich der Reihenfolge der Bitpackung bewusst sein - ich denke, es ist zuerst MSB, aber dies kann von der Implementierung abhängen. Überprüfen Sie auch, wie die Felder Ihres Compilers Byte-Grenzen überschreiten.

Sie können dann die einzelnen Werte wie zuvor lesen, schreiben und testen.


Verwenden der Standard-C ++ - Bibliothek: std::bitset<N> .

Oder die Boost Version: boost::dynamic_bitset .

Es ist nicht nötig, selbst zu rollen:

#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

Die Boost-Version ermöglicht ein Bitset mit Laufzeitgröße im Vergleich zu einem Standardbibliothek mit Kompilierungszeit.


Versuchen Sie eine dieser Funktionen in der Sprache C, um das n-Bit zu ändern:

char bitfield;

// Start at 0th position

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

Oder

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

Oder

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;
}

Dieses Programm kann jedes Datenbit von 0 auf 1 oder 1 auf 0 ändern:

{
    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);
        }
    }
}

Visual C 2010 und möglicherweise viele andere Compiler verfügen über eine direkte Unterstützung für integrierte Bitoperationen. Überraschenderweise funktioniert dies, auch wenn der Operator sizeof () ordnungsgemäß funktioniert.

bool    IsGph[256], IsNotGph[256];

//  Initialize boolean array to detect printable characters
for(i=0; i<sizeof(IsGph); i++)  {
    IsGph[i] = isgraph((unsigned char)i);
}

Also, zu Ihrer Frage machen IsGph [i] = 1 oder IsGph [i] = 0 das Setzen und Löschen von Bools einfach.

Nicht druckbare Zeichen finden ...

//  Initialize boolean array to detect UN-printable characters, 
//  then call function to toggle required bits true, while initializing a 2nd
//  boolean array as the complement of the 1st.
for(i=0; i<sizeof(IsGph); i++)  {
    if(IsGph[i])    {
         IsNotGph[i] = 0;
    }   else   {
         IsNotGph[i] = 1;
    }
}

Beachten Sie, dass an diesem Code nichts "Besonderes" ist. Es behandelt ein bisschen wie eine ganze Zahl - was technisch ist. Eine 1-Bit-Ganzzahl, die 2 Werte und nur 2 Werte aufnehmen kann.

Ich habe diesen Ansatz einmal verwendet, um doppelte Darlehenssätze zu finden, wobei loan_number der ISAM-Schlüssel war und die 6-stellige Darlehensnummer als Index für das Bit-Array verwendet. Schnell und nach acht Monaten bewies das Mainframe-System, von dem wir die Daten erhielten, tatsächlich nicht richtig. Die Einfachheit von Bit-Arrays macht das Vertrauen in ihre Korrektheit sehr hoch - im Vergleich zum Beispiel zum Suchen.


Für den Anfänger möchte ich mit einem Beispiel etwas mehr erklären:

Beispiel:

value is 0x55;
bitnum : 3rd.

Der Operator & wird verwendet, um das Bit zu überprüfen:

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)

Toggle oder Flip:

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

| Operator: das Bit setzen

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

Da dies mit "embedded" gekennzeichnet ist, gehe ich davon aus, dass Sie einen Mikrocontroller verwenden. Alle oben genannten Vorschläge sind gültig und funktionieren (Lesen, Ändern, Schreiben, Vereinigungen, Strukturen usw.).

Während eines Oszilloskop-basierten Debugging war ich jedoch erstaunt, als ich herausfand, dass diese Methoden einen erheblichen Overhead in CPU-Zyklen verursachen, verglichen mit dem direkten Schreiben eines Werts in die PORTnSET / PORTnCLEAR-Register des Micro -frequente ISR-Schaltstifte.

Für diejenigen, die nicht vertraut sind: In meinem Beispiel hat das Mikro ein allgemeines Pin-State-Register PORTn, das die Ausgangspins widerspiegelt. Wenn Sie PORTn | = BIT_TO_SET verwenden, führt dies zu einem Read-Modify-Write in dieses Register. Die PORTnSET / PORTnCLEAR-Register nehmen jedoch eine '1' für "bitte machen Sie dieses Bit auf 1" (SET) oder "bitte machen Sie dieses Bit auf Null" (CLEAR) und eine "0" auf "den Pin in Ruhe lassen". Am Ende stehen also zwei Portadressen zur Verfügung, je nachdem, ob Sie das Bit setzen oder löschen (nicht immer bequem), aber eine viel schnellere Reaktion und weniger zusammengesetzten Code.


Ein bisschen einstellen

Verwenden Sie den bitweisen OR-Operator ( | ), um ein Bit festzulegen.

number |= 1UL << n;

Damit wird das n te Bit der number . n sollte Null sein, wenn Sie das 1 Bit usw. setzen möchten, bis n-1 , wenn Sie das n te Bit setzen möchten.

Verwenden Sie 1ULL wenn die number breiter als die unsigned long . Die Förderung von 1UL << n erfolgt erst, nachdem 1UL << n wobei es undefiniertes Verhalten ist, sich um mehr als die Breite eines long . Gleiches gilt für alle übrigen Beispiele.

Ein bisschen klären

Verwenden Sie den bitweisen AND-Operator ( & ), um ein wenig zu löschen.

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

Dadurch wird das n te Bit number . Sie müssen den Bitstring mit dem bitweisen NOT-Operator ( ~ ) und dann mit AND invertieren.

Ein bisschen umschalten

Der XOR-Operator ( ^ ) kann verwendet werden, um ein Bit umzuschalten.

number ^= 1UL << n;

Dadurch wird das n te Bit der number umgeschaltet.

Ein bisschen überprüfen

Sie haben nicht danach gefragt, aber ich könnte es genauso gut hinzufügen.

Um etwas zu überprüfen, verschieben Sie die Zahl n nach rechts und dann bitweise UND:

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

Dadurch wird der Wert des n ten Bits in das Variablenbit bit .

Ändern des n- ten Bits in x

Das Setzen des n ten Bits auf 1 oder 0 kann mit der folgenden Implementierung einer 2-er Komplement-C ++ - Implementierung erreicht werden:

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

Bit n wird gesetzt, wenn x 1 , und gelöscht, wenn x 0 . Wenn x einen anderen Wert hat, erhalten Sie Müll. x = !!x booleanize es auf 0 oder 1.

Um dies vom Zweierkomplement-Negationsverhalten unabhängig zu machen (wobei bei -1 alle Bits gesetzt sind, im Gegensatz zu einer 1-Komplement- oder Vorzeichen- / Größen-C ++ - Implementierung), verwenden Sie die vorzeichenlose Negation.

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

oder

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

Im Allgemeinen ist es eine gute Idee, für die portable Bit-Bearbeitung unsignierte Typen zu verwenden.

Im Allgemeinen ist es auch eine gute Idee, Code im Allgemeinen nicht zu kopieren / einzufügen. Daher verwenden viele Leute Präprozessor-Makros (wie die Community-Wiki-Antwort weiter unten ) oder eine Art Verkapselung.


Überprüfen Sie ein Bit an einer beliebigen Stelle in einer Variablen beliebigen Typs:

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

Verwendungsbeispiel:

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;
}

Hinweise: Dies ist darauf ausgelegt, schnell (aufgrund seiner Flexibilität) und nicht verzweigt zu sein. Es ergibt einen effizienten SPARC-Maschinencode, wenn Sun Studio 8 kompiliert wird. Ich habe es auch mit MSVC ++ 2008 auf amd64 getestet. Es ist möglich, ähnliche Makros zum Setzen und Löschen von Bits zu erstellen. Der Hauptunterschied dieser Lösung im Vergleich zu vielen anderen Lösungen besteht darin, dass sie für jeden Ort in nahezu allen Arten von Variablen funktioniert.


Benutze das:

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

    return num;
}

Die andere Option ist die Verwendung von Bitfeldern:

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

struct bits mybits;

Definiert ein 3-Bit-Feld (eigentlich sind es drei 1-Bit-Felder). Bitoperationen werden jetzt etwas einfacher (haha):

Ein wenig setzen oder löschen:

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

Um ein wenig zu wechseln:

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

Ein bisschen überprüfen:

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

Dies funktioniert nur bei Bitfeldern mit fester Größe. Andernfalls müssen Sie auf die in den vorherigen Beiträgen beschriebenen Bit-Twiddling-Techniken zurückgreifen.


Diese Antwort ist eher eine Ergänzung und eine leichte Modifikation der obigen Antworten.

In einigen Versionen von Visual Studio (und möglicherweise anderen Compilern) gibt es einen Fehler, der wirklich nervig ist und keinen Sinn ergibt. Wenn Sie also Ihre swap Funktion wie swap deklarieren / definieren:

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

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

}

... der Compiler wird Sie anschreien, wenn Sie die swap Funktion aufrufen:

Dies hat etwas damit zu tun, dass eine friend Funktion aufgerufen wird und this Objekt als Parameter übergeben wird.

Um dies zu vermeiden, verwenden Sie das Schlüsselwort friend nicht und definieren die swap Funktion neu:

void swap(A& other) {

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

}

Diesmal können Sie einfach swap aufrufen und other , was den Compiler glücklich macht:

Schließlich müssen Sie keine friend Funktion verwenden, um 2 Objekte zu tauschen. Es ist ebenso sinnvoll, eine swap mit einem other Objekt als Parameter zu swap .

Sie haben bereits Zugriff auf this Objekt, daher ist es technisch überflüssig, es als Parameter zu übergeben.







c++ c bit-manipulation bitwise-operators