[[Template core/front/global/updateWarning is throwing an error. This theme may be out of date. Run the support tool in the AdminCP to restore the default theme.]]
سنتحدّث في هذا الموضوع عن أهم تطبيقات العمليات على البت والبايت وفوائد الإزاحة << و >> والعمليات المنطقية مثل AND, OR, XOR, NOT وكيف تُستخدم في العالم الحقيقي. موضوع سهل إلا أنه ليس بديهي ولن تكتشفه الا بالتجربة والممارسة وتحتاجه إذا كنت تعمل في مجال البرمجة المتقدمة كبرمجة النظم والعتاد وحتى بعض مجالات البرمجة العادية.
سنتطرق أولاً لموضوعات أخرى كمقدمة لهذا الموضوع وتعتبر أساسيات فقط لإزالة بعض الأفكار الخاطئة وتسليط الضوء على بعض المفاهيم. سأسخدم الـC لإنها أقرب لغة للإنسان وللحاسوب, في الوسط, الا أنه فعياً يُمكنك التطبيق بأي لغة ولا علاقة لهذا الموضوع بالـC بل مرتبط بمجال علوم الحاسب.
سنتحدث عن :
* كيفية تقطيع البتات, كتجزئة 0x11223344 إلى أربع بايتات منفصلة 0x11, 0x22, 0x33, 0x44.
* كيفية وصل البتات, كوصل أربع بايتات منفصلة 0x11, 0x22, 0x33, 0x44 لنحصل على dword تساوي 0x11223344.
* كيفية الحذف البايتات, كحذف الجزء 0x22 من 0x11223344 ليصبح 0x11003344.
* كيفية جعل متغير unsigned char يحمل 8 معلومات مختلفة و unsigned char long يحمل 32 معلومة مختلفة.
* نظرة على حقول البتات bit fields.
* مفهوم الرايات flags وإستخداماته.
لنتحدّث أولاً عن أشهر أنظمة العد, لن أدقق فيها بل فقط لتسليط الضوء على بعض الأشياء حولها. هناك أربع أنظمة عدّ مستخدمة في الحواسيب وهي على الترتيب حسب شيوع إستخدامها :
* نظام العد العشري. وأعداده الأساسية :
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
* نظام العد السداسي عشري. وأعداده الأساسية :
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f
* نظام العد الثنائي. ويُمثل بـ:
0, 1
* والنظام الأقل إستخدام النظام الثماني. وأعداده :
0, 1, 2, 3, 4, 5, 6, 7
إذا أردت التحويل بين أنظمة العد هذه يمكنك إستخدام الحاسبة في ويندوز لذلك :
نحن كبشر نعرف النظام العشري منذ طفولتنا فلاداعي للحديث عنه والنظام الثماني نادر الإستخدام ولم يسبق أن إضطررت للتعامل معه فدائماً مايوفي النظام السداسي العشري عنه, لذا لن أتحدث عنه. يهمنا كثيراً السداسي عشري والنظام الثنائي.
النظام الثنائي ليس بنظام حقيقي وغير مُستخدم عند مستوى البرامج بل يُستخدمه العتاد المادي. ما أريد قوله أنك لاتتوقع في يوم أن تكتب في برنامج:
01000001
ويطبع لك حرف الـA. ولكن يُمكن طباعته لو فتحت الحاسوب و عبثت في الذاكرة بحيث تشحن مكثف وفرغت شحنة خمس مكثفات بعده وتشحنت المكثف السابع وفرغت شحنة المكثف الثامن. ولو أردت إرسال رسالة تحمل الحرف A عبر الشبكة فسترسل نبضة كهربائية وبعد خمس ثوانٍ سترسل نبظة أخرى وتتأخر ثانية واحدة. ربما سيظهر لصديقك حينها حرف الـA.
كما رأيت أنه في الحقيقة لايوجد 0 ولايوجد 1 . فالصفر إمّا "يُعبر" عن عدم وجود الشحنة الكهربائية أو حالة عدم مرور تيار كهربائي خلال مدة زمنية محددة.والواحد "يُعبر" عن وجود الشحنة الكهربائية أو حالة مُرور تيار كهربائي خلال مُدة زمنية محددة.
النظام السداسي عشري أو الـhex مهم. فهو صورة أبسط وأقرب للنظام الثنائي. في النظام السداسي عشري, كل خانة تقابل 4 بتات bits من النظام الثنائي. مثلاً لو كتبت (سأستخدم 0x من الآن وصاعداً لتمثيل الأعداد السداي عشرية) :
0xf1
فبدون أن أحسب وأعمل شيء أعرف أنه يقابل 8 بتات "على الأكثر" في النظام الثنائي والنتيجة تؤكد ذلك بعد الحساب:
0xf1 = 11110001
حيث أن كل خانة تقابل 4 بتات ولدينا خانتين أي مجموعها 8 بتات :
0xf = 1111 0x1 = 0001
هناك أرقام مُميزة رجاء تذكرهما, وهما 0 و الـ1. فالصفر نفسه صفر في جميع أنظمة العد والواحد كذلك. أيضاً تذكر هذه المُتستلة (أسميها متسلسة أحجام الذاكرة) :
[math]2^n[/math]
لو أخذنا أول ثمان أرقام مثلا من هذه المُتسلسلة ونظرنا لها بعدّة أنظمة :
لابد أنك لاحظت النمط خصوصاً في النظام الثنائي, فهناك واحد وفي كل مرة يزيد صفر على اليمين. تذكر هذه المتسلسلة جيداً فسنستخدمها لاحقا.
لنتحدث الآن عن أشهر وحدات القياس في معالجات أنتل الـ32 بت والمساماة 86x, جميع الأحجام التي سأذكرها تقابل الضعف في حواسيب الـ64 بت. هناك أربع وحدات شهيرة وهي :
* البت bit وتقابل خانة واحدة في الثنائي:
1
* الحرف أوالبايت byte وتقابل 8 بتات في الثنائي :
11111111
* الكلمة word وتقابل 16 بايت في الثنائي وتقابل أيضا حرفين\بايتين :
11111111 11111111
* الكلمة المضاعفة double word أو فقط dword وتقابل 32 بايت في الثنائي وأيضاً 4 حروف\بايتات :
11111111 11111111 11111111 11111111
يُمكن تمثيل تلك الأحجام في C , بإسثناء البت , بإستخدام أنواعها الأساسية, char, short, int, long. فلكل نوع من تلك الأنواع حجم محدد يقابل تلك الأحجام :
* حجم int "قد يساوي" في بعض المُصرفات بايتين word وقد يساوي أيضاً أربع بايتات dword (لذا سنتجنبها).
* حجم long يساوي أربع بايتات أي dword, وهو المناسب لتمثيل عنوانين الذاكرة والمسجلات في حواسيب 32بت.
إذا أردت تمثيل البايت فإستخدم unsigned char ولمتثيل الكلمة إستخدم unsigned short ولتمثيل الكلمة المضاعفة إستخدم unsigned long.
سبب إستخدمنا لـunsigned أنها تُمكننا من إستخدام كامل البتات لهذا المتغير. مثلاً unsigned char. يمكون مجالة من صفر 0 :
00000000
إلى 255 0xff :
11111111
في حال إستخدمت char فقط, والتي فعلياً تكافئ signed char, فإن البت الأخير من اليسار فسيستخدم للتحديد الإشارة ,يُسمى هذا البت بـmost significant bit أو بت تحديد الإشارة كما أفضل تسميته, فإذا كان 1 فيعني أن العدد سالب وإذا كان 0 فسيعني أن العدد موجب. هذا يعني أيضاً أنك ستحصل فقط على 7 بتتات ليصبح المدى من -127 :
1 | 1111111
إلى 127 :
0 | 1111111
ونفس الشيء ينطبق على الأنواع الأخرى. إنظر لهذا البرنامج :
#include <stdio.h>
int main(int argc, char **argv) {
unsigned long i = 1; signed long j = -1;
if( j > i ) printf("-1 is greater than 1\n"); else printf("1 is greater than -1\n");
return 0; }
من البديهي أنّ 1 أكبر من -1, إلا أن البرنامج يخطئ وسيقول أنّ -1 أكبر من 1. حولها للنظام الثنائي وستفهم السبب :
طبعاً هذا البرنامج خاطئ ولا تحاول أن تقارن بين نوعين أحدهما unsigned والآخر signed لأنهما فعلياً نوعين مختلفين ولو تشابها بكونهما long.
قد تجد في بعض البرامج شيء مثل :
#include <stdio.h>
int main(int argc, char **argv) { unsigned char number = (unsigned char) -1;
printf("0x%.2x\n", number);
return 0; }
هذه طريقة لملئ جميع البتات والحصول على القيمة القصوى للمتغير, سيكون 0xff أو 255 في حالتنا. لكن لاتنسى أن تعمل casting للمتغير, لهذا وضعنا (unsigned char).
الآن بعد هذا الحديث أعتقد أنك أصبحت جاهز لعمليات تقطيع ووصل وحذف البتتات والبايتات وبعضاً من تطبيقاته.
تقطيع ووصل وحذف البتتات والبايتات تطبيق للإزاحة لليمين >> والإزاحة لليسار << و العمليات المنطقية :
* العملية AND وجدولها:
+---+---+---------+ | x | y | x AND y | +---+---+---------+ | 0 | 0 | 0 | | 0 | 1 | 0 | | 1 | 0 | 0 | | 1 | 1 | 1 | +---+---+---------+
تعطي العملية AND الرقم 1 إذا كان المتغيير الأول يساوي 1 "و" الثاني يساوي واحد.
رمزها & في C :
unsigned long = 1 & 0;
* العملية OR وجدولها :
+---+---+---------+ | x | y | x OR y | +---+---+---------+ | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 1 | +---+---+---------+
تعطي العملية OR إذا كان المتغيير الأول يساوي 1 "أو" الثاني يساوي واحد أو كان كلاهما يساوي 1.
رمزها | في C :
unsigned long = 1 | 0;
* العملية XOR وجدولها :
+---+---+---------+ | x | y | x XOR y | +---+---+---------+ | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 0 | +---+---+---------+
العملية XOR تعطي 1 إذا إختلف أحد المتغييرين عن الأخر. كأن يكون الأول 1 والثاني 0 أو العكس. إذا كانا متشابهيين فستعطي 0.
رمزها ^ في C :
unsigned long = 1 ^ 0;
* العملية NOT وجدولها :
+---+-------------+ | x | NOT x | +---+-------------+ | 0 | 1 | | 1 | 0 | +---+-------------+
العملية NOT تعكس أي رقم يأتيها.
رمزها ~ في C :
unsigned long = ~0;
لنبدأ مع إزاحة البتات. هناك نوعين من الإزاحة, الإزاحة لليمين << وللإزاحة لليسار >>. عندما نكتب :
#include <stdio.h>
int main(int argc, char **argv) { unsigned char number = 0xff;
number >>= 1; /* OR number = number >> 1 */
printf("0x%.2x\n", number);
return 0; }
فسيطبع البرنامج 0x7f. تقابل 0xff :
11111111
عندما أزحنا الرقم بت واحد لليمين أصبح يساوي 0x7f وبالثنائي :
01111111
وكأن المعالج قام بحذف بت من اليمين وعوّض مكانه في اليسار بصغر. كذلك بالنسبة للإزاحه لليسار, إلا أن المعالج يقوم بالعكس. مثلاً هذا البرنامج :
#include <stdio.h>
int main(int argc, char **argv) { unsigned char number = 0xff;
number <<= 1; /* OR number = number << 1 */
printf("0x%.2x\n", number);
return 0; }
سيطبع 0xfe. حيث أزاح الرقم 0xff لليسار ليصبح 0xfe :
11111110
لنأخذ أول تطبيق عملي لتقطيع البتات. لو كان لدينا هذا الرقم 0x1122 ونريد أن نقطعه ليصبح 0x11 فقط. يُمكننا إزاحته بايت واحد لليسار , أي 8 بتات :
#include <stdio.h>
int main(int argc, char **argv) { unsigned short number = 0x1122;
number >>= 8; /* OR number = number >> 8 */
printf("0x%.2x\n", number);
return 0; }
لو شغلناه فسيطبع 0x11. لو أردنا أن نقطع 0x22 فقط, هنا يأتي دور العملية AND.
تقوم العملية AND بتطبيق الجدول :
+---+---+---------+ | x | y | x AND y | +---+---+---------+ | 0 | 0 | 0 | | 0 | 1 | 0 | | 1 | 0 | 0 | | 1 | 1 | 1 | +---+---+---------+
على كامل المتغير. مثلاً لو كان لدينا متغيرين unsigned short أحدهما يحتوي 0x1100 وقمنا بتطبيق AND مع آخر يحتوي 0x11 فستعطي 0x00. تقابل 0x1100 في الثنائي (تذكر أننا نتعمل مع متغير unsigned short حجمه 16بت وأن كل خانتين من النظام السداسي عشري يقابل بايت أو 8 بتات) :
00010001 00000000
وتقابل 0x11 :
00000000 00010001
سيتم تطبيق العلاقة AND على كامل الحيز في الذاكرة :
00010001 00000000 00000000 00010001 AND ----------------- 00000000 00000000
يُمكننا الإستفادة من AND لقطع 0x22 من 0x1122 بالطريقة التالية 0x1122 & 0xff :
#include <stdio.h>
int main(int argc, char **argv) { unsigned short number = 0x1122;
number &= 0xff; /* OR number = number & 0xff */
printf("0x%.2x\n", number);
return 0; }
وستعطي 0x22. كيف حدث هذا؟ تقابل 0x1122 في الثنائي :
00010001 00100010
وتقابل 0xff :
00000000 11111111
عندما قمنا بتطبيق العلاقة AND بينهما :
00010001 00100010 00000000 11111111 AND ----------------- 00000000 00100010
لم يتأثر الشق الأول بينما أصبح الشق الأيسر أصفار والصفر على اليسار لامعنى له.
إنظر لهذا البرنامج والذي يقوم بتقطيع كلمة مضاعفة 0x11223344 إلى أربع بايتات 0x11 و0x22 و0x33 و0x44 وذلك بالإستفادة من الإزاحة << وعملية AND :
#include <stdio.h>
int main(int argc, char **argv) { const unsigned long number = 0x11223344;
لو كنت تستخدم برامج تحرير الصور والرسم, مثل برنامج الرسام أو فوتوشوب, ستلاحظ أنه يُمكنك إنشاء العديد من الألوان من ثلاث ألوان : الأحمر والأخضر والأزرق :
هذا النظام يُسمىّ نظام الـRGB إختصاراً لـ Red Green Blue. حيث يكون فيه اللون الأسود (أقل قيمة للون 0) :
Red = 0, Green = 0, Blue = 0
اللون الأبيض (أقصى قيمة للون 255):
Red = 255, Green = 255, Blue = 255
عينة للألوان الأساسية التي يُمكن تشكيلها من هذا النظام :
ويمكن عمل ألوان كثيرة أخرى بتعديل كثافة كُل لون , بشرط أن يكون الرقم بين 0-255 ,أو 0xff-0x00. يُخزّن الحاسوب هذه الألوان في 3 بايتات, 24 بت. حيث يكون البايت الأول من اليمين للون الأزرق ويليه اللون الأخضر ويليه الأحمر. مثلاً سبدو الأزرق , Red = 0, Green = 0, Blue = 255 , في الذاكرة هكذا 0x0000ff.
يظهر هذين الشكلين كثيراً لمن يتعاملون مع تطبيقات الوب. فإذا أراد مبرمج ويب أن يجعل لون خلفية الصفحة زرقاء فيُمكنه إستخدام إحدى التنسيقين : إما تنسيق الـhex أو السداسي عشري (يمكنك إنشاء ملف إسمه مثلاً foo.html ووضع هذا الكود فيه وفتحه بالمتصفح) :
هناك أيضاً مجال آخر يستخدم فكرة تقطيع ووصل البتات وهو العناوين كعناوين الـIP والـMAC address. لنأخذ الـIP كمثال.
لو كان لدينا عنوان الـIP هذا 192.0.10.0. هذه الصيغة هي الصيغة المقروءة والأكثر فهم للإنسان والا فالحاسوب يخزنها في متغير حجمة 4 بايت , 32بت, هكذا 0xc0000a00. فإذا أردنا التحويل من الصيغة الأولى للصيغة الثانية فيمكننا عمل ذلك هكذا :
إنظر لهذا البرنامج وفيه الدالة convert_string_to_ip تقوم بتحويل الـIP من نص مثل "192.0.10.0" إلى الصيغة 32بت 0xc0000a00 والدالة convert_ip_to_string التي تقوم بالعكس:
#include <stdio.h>
static unsigned long convert_string_to_ip(char *buffer); static void convert_ip_to_string(char *buffer, unsigned long ip);
int main(int argc, char **argv) { unsigned long ip_hex; char ip_string[16];
تلك الخطوتين نعرفها وليست ضرورية في مثالنا هذا لأننا نعرف أن number يحمل 0x1122. الا أنه في العالم الحقيقي لاتعرف ماقيمة ألمتغير number فقد تكون أي رقم, لذا قد تحتاج تلك الخطوتين.
الآن يأتي دور NOT وستنتج لنا الرقم المضاد من 0x1100 والذي إذا طبقنا 0x1122 معه ستُحذف 0x11 :
00010001 00000000 NOT ; ~0x1100 ----------------------- 11101110 11111111 ; 0xeeff
static void message_encrypt(char *message, unsigned long len, char key) { unsigned long i; for( i = 0 ; i < len ; i++ ) message[i] = message[i] ^ key; }
static void message_decrypt(char *cipher, unsigned long len, char key) { unsigned long i; for( i = 0 ; i < len ; i++ ) cipher[i] = cipher[i] ^ key; }
عند تشغيله :
> a.exe Encrypted message = Fz{a2{a2s2awq`wf2⌂waasuw3 Decrypted message = This is a secret message! >
لنعود لموضوعنا.
يُمكنك أيضاً بإستخدام العمليات على البتات أن تجعل متغير واحد مثل unsigned char يحمل 8 معلومات مختلفة. مثلاً لنفترض أنك مبرمج عتاد ولديك 8 مصابيح LED مختلفة وتريد أن تسجل حالة كُل مصباح. يُمكنك أن تستخدم بتات متغير من نوع unsigned char بحيث يُرمز كل بت من هذا المتغير إلى حالة المُصباح , 1 إذا كان مُشغل و 0 إذا كان مغلق :
قُمنا بإنشاء بُنية تحتوي على عضو unsigned char حجمه طبعاً 8بتات. وُقُمنا بتوزيع تلك البتات على ثمانية مُتغيرات ledX . فالرمز ":" يُحدد كم بت ستحجز من unsigned char لهذا المُتغير. الآن يُمكن وضعه صفر بأي من هذه المُتغيرات هكذا ledgroup.led2 = 0 أو 1 هكذا ledgroup.led2 = 1. طبعاً خجم أيّ من ledX يساوي 1 بت تماماً وهذا يعني أنك إما أن تضع 0 أو 1 فقط.
هُناك إستخدام آخر شائع للعمليات على البتات ومهم جداً خصوصاً في مجال برمجة النظم وهي "الرايات" flags. فرضاً لو كان لديك نظام تصاريح يُوزع التصاريح على كائنات , مثلاً ملفات أو عمليات أو مجموعات مستخدمين وغيرها, وأردت أن تعطي هذا الكائن أي من هذه التصاريح :
* تصريح التنفيذ للسماح له بتشغيل برنامج مثلاً.
* تصريح قراءة للسماح له بقراءة ملف مثلاً.
* تصريح للكتابة قراءة للسماح له بالكتابة في ملف مثلاً.
بدل إنشاء 3 متيغيرات مستقله وكتابة أكواد طويلة لتتبع كل تصريح, فُيمكنك إستغلال العمليات على البتات وتحديداً AND و OR لعمل هذا الشيء بسهولة :
الا أنني أفضل كثيراً إستخدام الطريقة الأولى وحصر الثوابت في مجموعة لتنسيق البرنامج وليظهر بشكل أفضل.
ماقمنا به أننا أنشأنا مجموعة ثوابت إسمها PermissionFlags وتحتوي على أسماء نوع كل تصريح وقيمته :
* تصريح التنفيذ PERMISSION_EXECUTE وقيمته :
1 << 0
والتي تساوي أيضاً 1 أو بالثنائي 1.
* تصريح القراءة PERMISSION_READ وقيمته :
1 << 1
والتي تساوي أيضاً 2 أو بالثنائي 10.
* تصريح الكتابة PERMISSION_WRITE وقيمته :
1 << 2
والتي تساوي أيضاً 4 أو بالثنائي 100.
طبعاً لم نختر تلك القيم عشوائياً, لاحظ أن الرقم الثنائي في كُل مرة يزيد صفر على اليمين. لو شغلت البرنامج فسيطبع :
> a.exe PERMISSION_READ is passed >
لو أردنا إعطاء صلاحية القراءة في برنامجنا مثلاً, فسنستخدم طريقة مشابهة لطريقة الدالة check_permitions ونمرر لها راية flag , في حالتنا مررنا PERMISSION_READ. حيث ستطبق الدالة علاقة AND بين الرقم المُمر وبين كل الرايات الذي يُمكن تمريرها. إذا لم يُعطي ناتج العلاقة بين الراية وهذا الرقم الممر صفر, فهذا يعني أن الراية مُررت.
نحن مررنا PERMISSION_READ والتي تُساوي في الثنائي 010, عندما طبقت الدالة العلاقة بين 010 وقيمة PERMISSION_EXECUTE والتي تساوي 001 أعطت 000 :
طالما أعطت صفر, فهذا يعني أن الراية المُمررة ليست بـPERMISSION_EXECUTE. ولكن عندما قامت الدالة بتطبيق علاقة الـAND مع الراية التي مررناها و PERMISSION_READ, فلم تُعطي الصفر :
هناك الكثير من الدوال حولنا والتي تأخذ رايات كمعاملات مثل الدالة MessageBox والتي تُظهر الرسائل في ويندوز, هذه عينة من الرايات التي يُمكننا تمريرها للدالة MessageBox:
يُمكن تمرير الراية MB_OK للدالة MessageBox كي يظهر زر OK هكذا :
تحدثنا عن الكثير من الموضوعات حول العمل مع البتات والبايتات والتلاعب بها. أو أن أنبه على أنك إذا رأيت شرح يتضمّن العمل على البتات كعند العمل مع العتاد أو الخوزميات والتشفير والتعامل مع الملفات فأغلب الظن أنك ستحتاج للجوء للعمليات التي ذكرناها.
تم النشر منذ (معدل)
السلام عليكم ورحمة الله وبركاته
سنتحدّث في هذا الموضوع عن أهم تطبيقات العمليات على البت والبايت وفوائد الإزاحة << و >> والعمليات المنطقية مثل AND, OR, XOR, NOT وكيف تُستخدم في العالم الحقيقي. موضوع سهل إلا أنه ليس بديهي ولن تكتشفه الا بالتجربة والممارسة وتحتاجه إذا كنت تعمل في مجال البرمجة المتقدمة كبرمجة النظم والعتاد وحتى بعض مجالات البرمجة العادية.
سنتطرق أولاً لموضوعات أخرى كمقدمة لهذا الموضوع وتعتبر أساسيات فقط لإزالة بعض الأفكار الخاطئة وتسليط الضوء على بعض المفاهيم. سأسخدم الـC لإنها أقرب لغة للإنسان وللحاسوب, في الوسط, الا أنه فعياً يُمكنك التطبيق بأي لغة ولا علاقة لهذا الموضوع بالـC بل مرتبط بمجال علوم الحاسب.
سنتحدث عن :
* كيفية تقطيع البتات, كتجزئة 0x11223344 إلى أربع بايتات منفصلة 0x11, 0x22, 0x33, 0x44.
* كيفية وصل البتات, كوصل أربع بايتات منفصلة 0x11, 0x22, 0x33, 0x44 لنحصل على dword تساوي 0x11223344.
* كيفية الحذف البايتات, كحذف الجزء 0x22 من 0x11223344 ليصبح 0x11003344.
* كيفية جعل متغير unsigned char يحمل 8 معلومات مختلفة و unsigned char long يحمل 32 معلومة مختلفة.
* نظرة على حقول البتات bit fields.
* مفهوم الرايات flags وإستخداماته.
لنتحدّث أولاً عن أشهر أنظمة العد, لن أدقق فيها بل فقط لتسليط الضوء على بعض الأشياء حولها. هناك أربع أنظمة عدّ مستخدمة في الحواسيب وهي على الترتيب حسب شيوع إستخدامها :
* نظام العد العشري. وأعداده الأساسية :
* نظام العد السداسي عشري. وأعداده الأساسية :
* نظام العد الثنائي. ويُمثل بـ:
* والنظام الأقل إستخدام النظام الثماني. وأعداده :
إذا أردت التحويل بين أنظمة العد هذه يمكنك إستخدام الحاسبة في ويندوز لذلك :
نحن كبشر نعرف النظام العشري منذ طفولتنا فلاداعي للحديث عنه والنظام الثماني نادر الإستخدام ولم يسبق أن إضطررت للتعامل معه فدائماً مايوفي النظام السداسي العشري عنه, لذا لن أتحدث عنه. يهمنا كثيراً السداسي عشري والنظام الثنائي.
النظام الثنائي ليس بنظام حقيقي وغير مُستخدم عند مستوى البرامج بل يُستخدمه العتاد المادي. ما أريد قوله أنك لاتتوقع في يوم أن تكتب في برنامج:
ويطبع لك حرف الـA. ولكن يُمكن طباعته لو فتحت الحاسوب و عبثت في الذاكرة بحيث تشحن مكثف وفرغت شحنة خمس مكثفات بعده وتشحنت المكثف السابع وفرغت شحنة المكثف الثامن. ولو أردت إرسال رسالة تحمل الحرف A عبر الشبكة فسترسل نبضة كهربائية وبعد خمس ثوانٍ سترسل نبظة أخرى وتتأخر ثانية واحدة. ربما سيظهر لصديقك حينها حرف الـA.
كما رأيت أنه في الحقيقة لايوجد 0 ولايوجد 1 . فالصفر إمّا "يُعبر" عن عدم وجود الشحنة الكهربائية أو حالة عدم مرور تيار كهربائي خلال مدة زمنية محددة.والواحد "يُعبر" عن وجود الشحنة الكهربائية أو حالة مُرور تيار كهربائي خلال مُدة زمنية محددة.
النظام السداسي عشري أو الـhex مهم. فهو صورة أبسط وأقرب للنظام الثنائي. في النظام السداسي عشري, كل خانة تقابل 4 بتات bits من النظام الثنائي. مثلاً لو كتبت (سأستخدم 0x من الآن وصاعداً لتمثيل الأعداد السداي عشرية) :
فبدون أن أحسب وأعمل شيء أعرف أنه يقابل 8 بتات "على الأكثر" في النظام الثنائي والنتيجة تؤكد ذلك بعد الحساب:
حيث أن كل خانة تقابل 4 بتات ولدينا خانتين أي مجموعها 8 بتات :
هناك أرقام مُميزة رجاء تذكرهما, وهما 0 و الـ1. فالصفر نفسه صفر في جميع أنظمة العد والواحد كذلك. أيضاً تذكر هذه المُتستلة (أسميها متسلسة أحجام الذاكرة) :
[math]2^n[/math]
لو أخذنا أول ثمان أرقام مثلا من هذه المُتسلسلة ونظرنا لها بعدّة أنظمة :
لابد أنك لاحظت النمط خصوصاً في النظام الثنائي, فهناك واحد وفي كل مرة يزيد صفر على اليمين. تذكر هذه المتسلسلة جيداً فسنستخدمها لاحقا.
لنتحدث الآن عن أشهر وحدات القياس في معالجات أنتل الـ32 بت والمساماة 86x, جميع الأحجام التي سأذكرها تقابل الضعف في حواسيب الـ64 بت. هناك أربع وحدات شهيرة وهي :
* البت bit وتقابل خانة واحدة في الثنائي:
* الحرف أوالبايت byte وتقابل 8 بتات في الثنائي :
* الكلمة word وتقابل 16 بايت في الثنائي وتقابل أيضا حرفين\بايتين :
* الكلمة المضاعفة double word أو فقط dword وتقابل 32 بايت في الثنائي وأيضاً 4 حروف\بايتات :
يُمكن تمثيل تلك الأحجام في C , بإسثناء البت , بإستخدام أنواعها الأساسية, char, short, int, long. فلكل نوع من تلك الأنواع حجم محدد يقابل تلك الأحجام :
* حجم char يساوي بايت byte واحد.
* حجم short يساوي بايتين أي أنه يساوي word.
* حجم int "قد يساوي" في بعض المُصرفات بايتين word وقد يساوي أيضاً أربع بايتات dword (لذا سنتجنبها).
* حجم long يساوي أربع بايتات أي dword, وهو المناسب لتمثيل عنوانين الذاكرة والمسجلات في حواسيب 32بت.
إذا أردت تمثيل البايت فإستخدم unsigned char ولمتثيل الكلمة إستخدم unsigned short ولتمثيل الكلمة المضاعفة إستخدم unsigned long.
سبب إستخدمنا لـunsigned أنها تُمكننا من إستخدام كامل البتات لهذا المتغير. مثلاً unsigned char. يمكون مجالة من صفر 0 :
إلى 255 0xff :
في حال إستخدمت char فقط, والتي فعلياً تكافئ signed char, فإن البت الأخير من اليسار فسيستخدم للتحديد الإشارة ,يُسمى هذا البت بـmost significant bit أو بت تحديد الإشارة كما أفضل تسميته, فإذا كان 1 فيعني أن العدد سالب وإذا كان 0 فسيعني أن العدد موجب. هذا يعني أيضاً أنك ستحصل فقط على 7 بتتات ليصبح المدى من -127 :
إلى 127 :
ونفس الشيء ينطبق على الأنواع الأخرى. إنظر لهذا البرنامج :
من البديهي أنّ 1 أكبر من -1, إلا أن البرنامج يخطئ وسيقول أنّ -1 أكبر من 1. حولها للنظام الثنائي وستفهم السبب :
طبعاً هذا البرنامج خاطئ ولا تحاول أن تقارن بين نوعين أحدهما unsigned والآخر signed لأنهما فعلياً نوعين مختلفين ولو تشابها بكونهما long.
قد تجد في بعض البرامج شيء مثل :
هذه طريقة لملئ جميع البتات والحصول على القيمة القصوى للمتغير, سيكون 0xff أو 255 في حالتنا. لكن لاتنسى أن تعمل casting للمتغير, لهذا وضعنا (unsigned char).
الآن بعد هذا الحديث أعتقد أنك أصبحت جاهز لعمليات تقطيع ووصل وحذف البتتات والبايتات وبعضاً من تطبيقاته.
تقطيع ووصل وحذف البتتات والبايتات تطبيق للإزاحة لليمين >> والإزاحة لليسار << و العمليات المنطقية :
* العملية AND وجدولها:
تعطي العملية AND الرقم 1 إذا كان المتغيير الأول يساوي 1 "و" الثاني يساوي واحد.
رمزها & في C :
* العملية OR وجدولها :
تعطي العملية OR إذا كان المتغيير الأول يساوي 1 "أو" الثاني يساوي واحد أو كان كلاهما يساوي 1.
رمزها | في C :
* العملية XOR وجدولها :
العملية XOR تعطي 1 إذا إختلف أحد المتغييرين عن الأخر. كأن يكون الأول 1 والثاني 0 أو العكس. إذا كانا متشابهيين فستعطي 0.
رمزها ^ في C :
* العملية NOT وجدولها :
العملية NOT تعكس أي رقم يأتيها.
رمزها ~ في C :
لنبدأ مع إزاحة البتات. هناك نوعين من الإزاحة, الإزاحة لليمين << وللإزاحة لليسار >>. عندما نكتب :
فسيطبع البرنامج 0x7f. تقابل 0xff :
عندما أزحنا الرقم بت واحد لليمين أصبح يساوي 0x7f وبالثنائي :
وكأن المعالج قام بحذف بت من اليمين وعوّض مكانه في اليسار بصغر. كذلك بالنسبة للإزاحه لليسار, إلا أن المعالج يقوم بالعكس. مثلاً هذا البرنامج :
سيطبع 0xfe. حيث أزاح الرقم 0xff لليسار ليصبح 0xfe :
لنأخذ أول تطبيق عملي لتقطيع البتات. لو كان لدينا هذا الرقم 0x1122 ونريد أن نقطعه ليصبح 0x11 فقط. يُمكننا إزاحته بايت واحد لليسار , أي 8 بتات :
لو شغلناه فسيطبع 0x11. لو أردنا أن نقطع 0x22 فقط, هنا يأتي دور العملية AND.
تقوم العملية AND بتطبيق الجدول :
على كامل المتغير. مثلاً لو كان لدينا متغيرين unsigned short أحدهما يحتوي 0x1100 وقمنا بتطبيق AND مع آخر يحتوي 0x11 فستعطي 0x00. تقابل 0x1100 في الثنائي (تذكر أننا نتعمل مع متغير unsigned short حجمه 16بت وأن كل خانتين من النظام السداسي عشري يقابل بايت أو 8 بتات) :
وتقابل 0x11 :
سيتم تطبيق العلاقة AND على كامل الحيز في الذاكرة :
يُمكننا الإستفادة من AND لقطع 0x22 من 0x1122 بالطريقة التالية 0x1122 & 0xff :
وستعطي 0x22. كيف حدث هذا؟ تقابل 0x1122 في الثنائي :
وتقابل 0xff :
عندما قمنا بتطبيق العلاقة AND بينهما :
لم يتأثر الشق الأول بينما أصبح الشق الأيسر أصفار والصفر على اليسار لامعنى له.
إنظر لهذا البرنامج والذي يقوم بتقطيع كلمة مضاعفة 0x11223344 إلى أربع بايتات 0x11 و0x22 و0x33 و0x44 وذلك بالإستفادة من الإزاحة << وعملية AND :
لو قمنا بتشغيله :
لو أردت تقطيع كلمة تحتوي 0x1234 إلى بايتات كل منها 0x1 و0x2 و0x3 و0x4 فيُمكنك التعديل عليه قليلاً ليصبح :
لو قمنا بتشغيله :
ماذا لو أردنا مثلاً وصل رقمين مثل 0x11 و0x22 ليصبحا 0x1122 ؟ هنا يأتي دور العملية OR. ويُمكننا عمل ذلك بالطريقة التالية :
لو قمت بتشغيله فسيطبع 0x1122. في البداية number يساوي صفر. عندما قُمنا بعمل AND بينه وبين 0x11 :
لم نقم بعمل شيء فعلياً, فـAND س مع صفر ستعطي س. ولكن الجزء الحقيقي يبدأ هنا :
قُمنا بإزاحة number والتي تحمل الآن القيمة 0x11 بايت واحد لليمين , أو 8 بتات , لتصبح 0x1100 :
وذلك لعمل مساحة لـ0x22. الآن قُمنا بوصل 0x1100 مع 0x22 بإستخدام العلاقة OR :
ليصبح لدينا 0x1122.
إنظر كيف نقُوم بوصل 0x11 و0x22 و0x33 و0x44 لتصبح 0x11223344.
لنرى تطبيقات هذه العمليات في العالم الحقيقي.
لو كنت تستخدم برامج تحرير الصور والرسم, مثل برنامج الرسام أو فوتوشوب, ستلاحظ أنه يُمكنك إنشاء العديد من الألوان من ثلاث ألوان : الأحمر والأخضر والأزرق :
هذا النظام يُسمىّ نظام الـRGB إختصاراً لـ Red Green Blue. حيث يكون فيه اللون الأسود (أقل قيمة للون 0) :
اللون الأبيض (أقصى قيمة للون 255):
عينة للألوان الأساسية التي يُمكن تشكيلها من هذا النظام :
ويمكن عمل ألوان كثيرة أخرى بتعديل كثافة كُل لون , بشرط أن يكون الرقم بين 0-255 ,أو 0xff-0x00. يُخزّن الحاسوب هذه الألوان في 3 بايتات, 24 بت. حيث يكون البايت الأول من اليمين للون الأزرق ويليه اللون الأخضر ويليه الأحمر. مثلاً سبدو الأزرق , Red = 0, Green = 0, Blue = 255 , في الذاكرة هكذا 0x0000ff.
يظهر هذين الشكلين كثيراً لمن يتعاملون مع تطبيقات الوب. فإذا أراد مبرمج ويب أن يجعل لون خلفية الصفحة زرقاء فيُمكنه إستخدام إحدى التنسيقين : إما تنسيق الـhex أو السداسي عشري (يمكنك إنشاء ملف إسمه مثلاً foo.html ووضع هذا الكود فيه وفتحه بالمتصفح) :
أو تنسيق الـrgb :
الشكل الأول #0000ff أقرب للحاسوب والشكل الآخر أقرب للإنسان. إذا أردنا التحويل من الصيغة الأولى للثانية, فُنستخدم طريقة تقطيع البتات :
وإذا أردنا التحويل من الـrgb للـhex :
إنظر لهذا البرنامج بـC , فهناك الدالة color_rgb_to_hex للتحويل للـhex والدالة color_rgb_to_hex للتحويل للـrgb :
هناك أيضاً مجال آخر يستخدم فكرة تقطيع ووصل البتات وهو العناوين كعناوين الـIP والـMAC address. لنأخذ الـIP كمثال.
لو كان لدينا عنوان الـIP هذا 192.0.10.0. هذه الصيغة هي الصيغة المقروءة والأكثر فهم للإنسان والا فالحاسوب يخزنها في متغير حجمة 4 بايت , 32بت, هكذا 0xc0000a00. فإذا أردنا التحويل من الصيغة الأولى للصيغة الثانية فيمكننا عمل ذلك هكذا :
وللتحويل من الصيغة 0xc0000a00 للصيغة المقروءة :
إنظر لهذا البرنامج وفيه الدالة convert_string_to_ip تقوم بتحويل الـIP من نص مثل "192.0.10.0" إلى الصيغة 32بت 0xc0000a00 والدالة convert_ip_to_string التي تقوم بالعكس:
لنفترض أن لدينا هذا البرنامج :
سيطبع البرنامج 0x1122. السؤال هنا ماذا لو أردنا أن "نحذف" 0x11 فقط لتصبح 0x0022 ؟ هُنا يأتي دور العملية NOT.
لو كان لدينا متغير حجمة بايتين, 16 بت, ويحتوي 0x00ff وقُمنا بتطبيق العملية NOT عليه فسيصبح 0xff00 :
ماحدث ببساطة أن كل 1 قلبت إلى 0 وكل 0 قلبت إلى 1 :
لو أردنا حذف 0x11 من 0x1122 فسنقوم بإزاحة 0x1122 إلى اليمين 8 بتات, بايت :
لإقتطاع 0x11 ومن ثُم نُزيحها لليسار 8 بتات :
تلك الخطوتين نعرفها وليست ضرورية في مثالنا هذا لأننا نعرف أن number يحمل 0x1122. الا أنه في العالم الحقيقي لاتعرف ماقيمة ألمتغير number فقد تكون أي رقم, لذا قد تحتاج تلك الخطوتين.
الآن يأتي دور NOT وستنتج لنا الرقم المضاد من 0x1100 والذي إذا طبقنا 0x1122 معه ستُحذف 0x11 :
الرقم المُضاد هو 0xeeff. الآن لنُطبقه مع 0x1122 :
كما ترى, حُذفت 0x11 وبقي 0x0022. لنرى البرنامج :
سيطبع الآن 0x0022. فعلياً يُمكننا إختصار آخر خطوتين, NOT و AND , بالعميلة XOR, الآن أتى دورها. فهي تُكافئ NOT وAND معاً حيث أنّ:
لنرى البرنامج :
هنُاك إستخدامات أخرى جميلة لـXOR, فهي تُستخدم لعمل تشفير خفيف (لاتستخدمه لإخفاء بيانات حساسة فهو تشفير ضعيف ويُمكن كسره بسهولة نسبية). لتشفير مثلاً 0x1234 بالمفتاح السري 0x3344 يُمكننا عمل :
وستعطينا البيانات المشفرة 0x2170. ولفك التشفير, يُمكننا عكس العملية بإستخدام البيانات المشفرة والمفتاح السري :
لتحصل على الرسالة الأصلية 0x1234. إنظر لهذا البرنامج الذي يُشفر رسالة ويفك تشفيرها :
عند تشغيله :
لنعود لموضوعنا.
يُمكنك أيضاً بإستخدام العمليات على البتات أن تجعل متغير واحد مثل unsigned char يحمل 8 معلومات مختلفة. مثلاً لنفترض أنك مبرمج عتاد ولديك 8 مصابيح LED مختلفة وتريد أن تسجل حالة كُل مصباح. يُمكنك أن تستخدم بتات متغير من نوع unsigned char بحيث يُرمز كل بت من هذا المتغير إلى حالة المُصباح , 1 إذا كان مُشغل و 0 إذا كان مغلق :
الا أن هناك طريقة أخرى شائعة الإستخدام أيضاً وهي بإستخدام "خانات البتات" bit fields. نفس البرنامج السابق يُمكن كتابته هكذا :
قُمنا بإنشاء بُنية تحتوي على عضو unsigned char حجمه طبعاً 8بتات. وُقُمنا بتوزيع تلك البتات على ثمانية مُتغيرات ledX . فالرمز ":" يُحدد كم بت ستحجز من unsigned char لهذا المُتغير. الآن يُمكن وضعه صفر بأي من هذه المُتغيرات هكذا ledgroup.led2 = 0 أو 1 هكذا ledgroup.led2 = 1. طبعاً خجم أيّ من ledX يساوي 1 بت تماماً وهذا يعني أنك إما أن تضع 0 أو 1 فقط.
هُناك إستخدام آخر شائع للعمليات على البتات ومهم جداً خصوصاً في مجال برمجة النظم وهي "الرايات" flags. فرضاً لو كان لديك نظام تصاريح يُوزع التصاريح على كائنات , مثلاً ملفات أو عمليات أو مجموعات مستخدمين وغيرها, وأردت أن تعطي هذا الكائن أي من هذه التصاريح :
* تصريح التنفيذ للسماح له بتشغيل برنامج مثلاً.
* تصريح قراءة للسماح له بقراءة ملف مثلاً.
* تصريح للكتابة قراءة للسماح له بالكتابة في ملف مثلاً.
بدل إنشاء 3 متيغيرات مستقله وكتابة أكواد طويلة لتتبع كل تصريح, فُيمكنك إستغلال العمليات على البتات وتحديداً AND و OR لعمل هذا الشيء بسهولة :
أويمكن كتابة البرنامج هكذا, بدل إستخدام مجموعة الثوابت enum نعرف الثوابت بإستخدام define :
الا أنني أفضل كثيراً إستخدام الطريقة الأولى وحصر الثوابت في مجموعة لتنسيق البرنامج وليظهر بشكل أفضل.
ماقمنا به أننا أنشأنا مجموعة ثوابت إسمها PermissionFlags وتحتوي على أسماء نوع كل تصريح وقيمته :
* تصريح التنفيذ PERMISSION_EXECUTE وقيمته :
والتي تساوي أيضاً 1 أو بالثنائي 1.
* تصريح القراءة PERMISSION_READ وقيمته :
والتي تساوي أيضاً 2 أو بالثنائي 10.
* تصريح الكتابة PERMISSION_WRITE وقيمته :
والتي تساوي أيضاً 4 أو بالثنائي 100.
طبعاً لم نختر تلك القيم عشوائياً, لاحظ أن الرقم الثنائي في كُل مرة يزيد صفر على اليمين. لو شغلت البرنامج فسيطبع :
لو أردنا إعطاء صلاحية القراءة في برنامجنا مثلاً, فسنستخدم طريقة مشابهة لطريقة الدالة check_permitions ونمرر لها راية flag , في حالتنا مررنا PERMISSION_READ. حيث ستطبق الدالة علاقة AND بين الرقم المُمر وبين كل الرايات الذي يُمكن تمريرها. إذا لم يُعطي ناتج العلاقة بين الراية وهذا الرقم الممر صفر, فهذا يعني أن الراية مُررت.
نحن مررنا PERMISSION_READ والتي تُساوي في الثنائي 010, عندما طبقت الدالة العلاقة بين 010 وقيمة PERMISSION_EXECUTE والتي تساوي 001 أعطت 000 :
طالما أعطت صفر, فهذا يعني أن الراية المُمررة ليست بـPERMISSION_EXECUTE. ولكن عندما قامت الدالة بتطبيق علاقة الـAND مع الراية التي مررناها و PERMISSION_READ, فلم تُعطي الصفر :
وهذا يعني أنّ الراية PERMISSION_READ مُررت. عندما طبقت العلاقة مع PERMISSION_WRITE لم تعطي صفر أي أنها لم تُمرر.
هذه الطريقة تُمكننا من تمرير أكثر من راية وليس راية واحدة. إنظر هنا, لُنمرر الرايتين PERMISSION_EXECUTE وPERMISSION_READ للدالة هكذا :
لاحظ أننا قُمنا بعمل علاقة AND بين الرايتين :
أي :
الآن لو شغلت البرنامج فسيطبع :
ماحدث مع الدالة أنّها عملت علاقة AND بين 011 وPERMISSION_EXECUTE فأعطت 001, طالما أنها لم تعطي صفر فهذا يعني أن الراية PERMISSION_EXECUTE مُررت :
قامت أيضاً بعمل علاقة AND بين 011 وPERMISSION_READ وأعطت 010 ممايفيد بأن الراية PERMISSION_READ مررت لأننا لم نحصل على صفر :
ولكن عندما قامت الدالة بعمل علاقة AND بين 011 وPERMISSION_WRITE فاناتج كان صفر :
وهذا يعني أنّ الراية PERMISSION_WRITE لم تُمرر.
هناك الكثير من الدوال حولنا والتي تأخذ رايات كمعاملات مثل الدالة MessageBox والتي تُظهر الرسائل في ويندوز, هذه عينة من الرايات التي يُمكننا تمريرها للدالة MessageBox:
يُمكن تمرير الراية MB_OK للدالة MessageBox كي يظهر زر OK هكذا :
ويُمكن أيضاً إضافة الراية MB_ICONWARNING لإظهار أيقونة تحذير :
وأيضاً يُمكن إضافة الراية MB_HELP لإضافة زرّ المساعدة HELP :
تحدثنا عن الكثير من الموضوعات حول العمل مع البتات والبايتات والتلاعب بها. أو أن أنبه على أنك إذا رأيت شرح يتضمّن العمل على البتات كعند العمل مع العتاد أو الخوزميات والتشفير والتعامل مع الملفات فأغلب الظن أنك ستحتاج للجوء للعمليات التي ذكرناها.
أتمنى أن تكون إستفدت من المقال.
بالتوفيق.
تم تعديل بواسطه Mr.Bشارك هذا الرد
رابط المشاركة
شارك الرد من خلال المواقع ادناه