c++ - and - bitwise operators شرح




كيف يمكنك ضبط ومسح وتبديل قطعة واحدة؟ (18)

كيف يمكنك ضبط ومسح وتبديل قطعة واحدة؟

لمعالجة مشكلة الترميز الشائعة عند محاولة تشكيل القناع:
1ليس دائمًا واسعًا بما فيه الكفاية

ما هي المشاكل التي تحدث عندما numberيكون نوع أوسع من 1؟
xقد يكون كبيرًا جدًا بالنسبة للتحول الذي 1 << xيؤدي إلى سلوك غير معروف (UB). حتى لو xلم يكن كبيرًا جدًا ، ~فقد لا يتجاهل أجزاءًا أكثر أهمية.

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

لضمان 1 واسعة بما فيه الكفاية:

يمكن أن تستخدم التعليمات البرمجية 1ullأو بشكل توضيحي (uintmax_t)1والسماح للمجمع بتحسين.

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

أو يلقي - مما يجعل لمسائل الترميز / المراجعة / الصيانة الحفاظ على فريق العمل الصحيح والحديث.

number |= (type_of_number)1 << x;

أو رقي بلطف عن 1طريق إجبار عملية الرياضيات التي هي على الأقل واسعة مثل نوع number.

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

كما هو الحال مع معظم عمليات التلاعب في البتات ، من الأفضل العمل مع أنواع غير موقعة بدلاً من تلك الموقعة

كيف يمكنك ضبط ، ومسح ، وتبديل بعض الشيء في C / C ++؟


وضع قليلا

استخدم عامل التشغيل OR بالبت ( | ) لتعيين بعض الشيء.

number |= 1UL << n;

سيؤدي ذلك إلى تحديد n البتات. يجب أن يكون n صفراً ، إذا كنت ترغب في ضبط 1 bit وهكذا على u n-1 ، إذا كنت تريد ضبط البتة th.

استخدم 1ULL إذا كان number أكبر من unsigned long ؛ لا يتم الترويج لـ 1UL << n حتى بعد تقييم 1UL << n حيث يتم تغيير السلوك غير المحدد بأكثر من عرض long . وينطبق الشيء نفسه على كل ما تبقى من الأمثلة.

تطهير قليلا

استخدم عامل التشغيل AND AND (لإلغاء مسح).

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

سيؤدي ذلك إلى مسح الجزء التاسع من number . يجب عكس سلسلة البت مع عامل التشغيل NOT bitwise ( ~ ) ، ثم AND.

تبديل قليلا

يمكن استخدام عامل التشغيل XOR ( ^ ) للتبديل بعض الشيء.

number ^= 1UL << n;

سيؤدي ذلك إلى تبديل الجزء n من number .

التحقق قليلا

أنت لم تسأل عن هذا ، لكن قد أضيفه أيضًا.

للتحقق من بعض الشيء ، قم برفع الرقم n إلى اليمين ، ثم إلى bitwise AND:

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

سيضع ذلك قيمة البتة number من number في bit المتغير.

تغيير البتة th إلى x

يمكن تحقيق ضبط البتة n إلى 1 أو 0 مع ما يلي في تنفيذ C ++ المتمم 2:

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

سيتم ضبط البت n إذا كان x 1 ، ويتم مسحه إذا كانت x 0 . إذا كان لدى x قيمة أخرى ، فستحصل على القمامة. x = !!x سيصبح منطقية إلى 0 أو 1.

لجعل هذا الأمر مستقلاً عن سلوك الإبطال المكمّل في 2 (حيث -1 تم تعيين كل البتات ، على خلاف التكملة 1 أو توقيع / حجم تطبيق C ++) ، استخدم نفي غير موقع.

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

أو

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

من المفيد عمومًا استخدام أنواع غير الموقعة من أجل معالجة البتات المحمولة.

من المفيد أيضًا بشكل عام عدم نسخ / لصق الكود بشكل عام والكثير من الأشخاص يستخدمون وحدات الماكرو preprocessor (مثل إجابة wiki للمنتدى باستمرار ) أو نوعًا ما من التغليف.


من snip-c.zip 's 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)))

حسنًا ، دعنا نحلل الأشياء ...

التعبير الشائع الذي يبدو أنك تواجه مشاكل فيه في كل هذه "(1L << (posn))". كل هذا يفعل هو خلق قناع مع بت واحد على والتي ستعمل مع أي نوع صحيح. تحدد الوسيطة "posn" الموضع الذي تريد فيه البت. إذا كانت posn == 0 ، فسيتم تقييم هذا التعبير إلى:

    0000 0000 0000 0000 0000 0000 0000 0001 binary.

إذا posn == 8 ، فإنه سيتم تقييمه

    0000 0000 0000 0000 0000 0001 0000 0000 binary.

وبعبارة أخرى ، فإنه ينشئ ببساطة حقل من 0 مع 1 في الموضع المحدد. الجزء الوحيد صعب في الماكرو BitClr () حيث نحتاج إلى تعيين بت 0 واحد في حقل 1. يتم إنجاز ذلك باستخدام مكمل 1 من نفس التعبير كما هو مشار إليه بواسطة عامل التلدة (~).

بمجرد إنشاء القناع يتم تطبيقه على الوسيطة كما تقترح ، من خلال استخدام معامل البتات و (&) ، أو (|) ، و xor (^). نظرًا لأن القناع من النوع الطويل ، فإن وحدات الماكرو ستعمل أيضًا على شارات char أو short's أو int أو longs.

خلاصة القول هي أن هذا هو الحل العام لفئة كاملة من المشاكل. وبالطبع ، من الممكن بل ومن المناسب إعادة كتابة ما يعادل أي من وحدات الماكرو هذه بقيم قناع صريحة في كل مرة تحتاج فيها ، ولكن لماذا تفعل ذلك؟ تذكر أن استبدال الماكرو يحدث في المعالج الأولي ، وبالتالي فإن الشفرة التي تم توليدها ستعكس حقيقة أن القيم تعتبر ثابتة من قبل المترجم - أي أنه بنفس كفاءة استخدام وحدات الماكرو العامة "لإعادة اختراع العجلة" في كل مرة تحتاج فيها إلى تفعل قليلا التلاعب.

غير مقتنع؟ إليك بعض كود الاختبار - لقد استخدمت Watcom C مع التحسين الكامل وبدون استخدام _cdecl لذا سيكون التفكيك الناتج نظيفًا قدر الإمكان:

---- [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 (مفكك)] -------------------------------------- ---------

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


أستخدم وحدات الماكرو المحددة في ملف رأس للتعامل مع مجموعة البت ومسحها:

/* 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))

إذا كنت تقوم بالكثير من التلاعب ، فقد ترغب في استخدام الأقنعة التي ستجعل كل شيء أسرع. الوظائف التالية سريعة جدًا ولا تزال مرنة (تسمح بتلويح بت في خرائط البتات من أي حجم).

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

ملاحظة ، لتعيين bit 'n' في عدد صحيح من 16 بت ، قم بما يلي:

TSetBit( n, &my_int);

الأمر متروك لك للتأكد من أن رقم البت يقع ضمن نطاق خريطة البت التي تقوم بتمريرها. لاحظ أن معالجات endian الصغيرة التي البايتات ، الكلمات ، dwords ، qwords ، إلخ ، خريطة بشكل صحيح لبعضها البعض في الذاكرة (السبب الرئيسي أن المعالجات endian قليلاً 'أفضل' من المعالجات endian كبيرة ، آه ، أشعر بحرب اللهب القادمة على...).


استخدم معاملات البتات: & |

لتعيين آخر بت في 000b :

foo = foo | 001b

للتحقق من البت الأخير في foo :

if ( foo & 001b ) ....

لمسح آخر بت في foo :

foo = foo & 110b

لقد استخدمت XXXb أجل الوضوح. من المحتمل أنك ستعمل مع تمثيل HEX ، بناءً على بنية البيانات التي تقوم بتغليف البت فيها.


الخيار الآخر هو استخدام حقول البت:

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

struct bits mybits;

يحدد مجال 3 بت (في الواقع ، انها ثلاث اللحامات 1 بت). أصبحت عمليات البت الآن قليلا (haha) أبسط:

لتعيين أو مسح بعض الشيء:

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

لتبديل بعض الشيء:

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

التحقق قليلا:

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

هذا يعمل فقط مع حقول بت حجم ثابت. خلاف ذلك ، يجب عليك اللجوء إلى تقنيات التلاعب قليلاً الموضحة في المشاركات السابقة.


باستخدام مكتبة C ++ القياسية: std::bitset<N> .

أو الإصدار Boost : boost::dynamic_bitset .

ليست هناك حاجة لللف الخاصة بك:

#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

يسمح إصدار Boost بتقطيع حجم وقت التشغيل مقارنةً بمجموعة bitset في وقت تجميع المكتبة القياسية .


من المفيد أحيانًا استخدام enum لتسمية البتات:

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

ثم استخدم الأسماء في وقت لاحق. أي أكتب

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

لتعيين واضحة والاختبار. بهذه الطريقة تخفي الأرقام السحرية عن بقية الشفرة.

بخلاف ذلك أؤيد حل جيريمي.


نظرًا لأنه تم وضع علامة على هذا "مضمّن" ، أفترض أنك تستخدم وحدة تحكم دقيقة. جميع الاقتراحات المذكورة أعلاه صالحة وتعمل (اقرأ -تعديل- الكتابة ، والنقابات ، والبنى ، وما إلى ذلك).

ومع ذلك ، خلال نوبة من تصحيح الأخطاء المستندة إلى الذبذبات ، دهشت عندما اكتشفت أن هذه الطرق لها تأثير كبير في دورات وحدة المعالجة المركزية مقارنة بكتابة قيمة مباشرة إلى سجلات PORTnSET / PORTnCLEAR الصغرى التي تحدث فرقًا حقيقيًا عندما تكون هناك حلقات ضيقة / عالية التردد دبابيس ISR ل.

بالنسبة لأولئك غير المألوفين: في المثال الخاص بي ، يحتوي الصغير على سجل PURN عام لحالة دبوس والذي يعكس دبابيس الإخراج ، وبذلك يتم تنفيذ PORTn | = BIT_TO_SET في القراءة - التعديل - الكتابة إلى هذا السجل. ومع ذلك ، تأخذ سجلات PORTnSET / PORTnCLEAR "1" ليعني "الرجاء جعل هذه البتة 1" (SET) أو "الرجاء جعل هذا بت صفر" (CLEAR) و "0" تعني "ترك الدبوس وحده". لذلك ، ينتهي بك الأمر مع اثنين من عناوين المنفذ اعتمادًا على ما إذا كنت تقوم بتحديد أو مسح البت (غير مناسب دائمًا) ولكن رد فعل أسرع بكثير وتعليمة برمجية أصغر حجمًا.


هذا البرنامج هو تغيير أي بت البيانات من 0 إلى 1 أو 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);
        }
    }
}

هنا هو المفضل الخاص بي الماكرو حسابية ، الذي يعمل لأي نوع من صفيف صحيح غير موقعة من unsigned char إلى size_t (وهو أكبر نوع يجب أن تكون فعالة للعمل مع):

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

لتعيين قليلا:

BITOP(array, bit, |=);

لمسح قليلا:

BITOP(array, bit, &=~);

لتبديل بعض الشيء:

BITOP(array, bit, ^=);

لاختبار قليلا:

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

إلخ


إصدار C ++ 11 templated (وضع في رأس):

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

جرب إحدى هذه الوظائف في لغة C لتغيير n بت:

char bitfield;

// Start at 0th position

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

أو

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

أو

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

متغير مستعمل

int value, pos;

قيمة - بيانات
نقاط البيع - موقف بت التي نحن مهتمون لتعيين، واضحة أو مفتاح
تعيين قليلا

value = value | 1 << pos;

واضح قليلا

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

تبديل قليلا

value = value ^ 1 << pos;

إذا كنت ترغب في تنفيذ جميع هذه العمليات مع البرمجة C في نواة لينكس ، فأقترح استخدام واجهات برمجة التطبيقات القياسية لنواة لينكس.

انظر 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

ملاحظة: هنا العملية بأكملها تحدث في خطوة واحدة. لذلك ، يتم ضمان جميع هذه الأجهزة أن تكون ذرية حتى في أجهزة الكمبيوتر التي تعمل بنظام SMP ، كما أنها مفيدة للحفاظ على الاتساق عبر المعالجات.


التوسيع على bitsetالجواب:

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

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




bitwise-operators