اذهب إلى المحتوى

السؤال

نشر

عندما أقوم بعمل حلقة تكرار while وأتحقق من أن قيمة المتغير x أكبر من أو تساوي -1 يتم طباعة رقم كبير في نهاية الحلقة،

#include <iostream>

// check if x is zero or bigger
bool check(int x);

int main()
{
    unsigned int x {100};
    while ( check(x) ) {
        std::cout << x << std::endl;
        --x;
    }

    return 0;
}

bool check(int x) {
    return x >= -1;
}

نتيجة الكود السابق هي طباعة الأرقام من 100 إلى صفر ثم أجد أنه تم طباعة الرقم 4294967295:
 

100
99
98
97
96
95
...
2
1
0
4294967295

ما سبب أنه يتم طباعة هذا الرقم الكبير؟ وكيف يمكن أن أتخلص منه؟

Recommended Posts

  • 3
نشر

يوجد في ++C نوعين من المتغيرات الرقمية، متغيرات بإشارة signed ومتغيرات بدون إشارة unsigned، ويتم تخزين هذه المتغيرات بطرق مختلفة، ويكمن الإختلاف بينهما هو أن المتغيرات بدون إشارة unsigned يمكنها تخزين أرقام أكبر من المتغيرات التي بإشارة، وذلك لأنها تستخدم bit إضافة (الـ bit الخاصة بالإشارة الموجبة او السالبة). ولفهم سبب حدوث المشكلة يجب أن نعرف النوع int وكيف يتم تخزينه في الذاكرة.

قيمة 1-بايت من نوع int يمكن أن يحمل 255 إحتمال من القيم، وهي ما بين -127 و +127 ، وبالتالي عند محالة تخزين رقم أكبر من 127 (مثل 180 على سبيل المثال) سيحدث شيء يسمى "طفح عدد صحيح integer overflow" وذلك يعني أن المتغير لا يمكنه أن يحمل قيمة أكبر من 127 أو أقل من -127 وسيؤدي هذا الأمر إلى حدوث خطأ أثناء عملية التصريف compiling في أغلب بيئات التطوير.

على الجانب الآخر يمكن للمتغيرات التي ليس لها إشارة unsigned أن تحمل 255 إحتمال من القيم أيضًا ولكن تكون هذه القيمة ما بين 0 إلى 255 وبالتالي يمكن تخزين الرقم 180 بدون مشكلة في نفس حجم الذاكرة، لكن تكمن المشكلة في هذا النوع أنه عند تخزين رقم أكبر من 255 سوف يحدث ما يسمى Unsigned integer overflow أي أن الأرقام الأكبر من 255 سوف تبدأ من صفر مجددًا:

#include <iostream>

int main()
{
    unsigned short x{ 65535 }; // النوع short يمكنه أن يحمل قيمة 65535 بحد أقصى
    std::cout << "x was: " << x << '\n';

    x = 65536; // 65536 is out of our range
    std::cout << "x is now: " << x << '\n';	// x = 0

    x = 65537; // 65537 is out of our range
    std::cout << "x is now: " << x << '\n';	// x = 1

    return 0;
}

ملاحظة: النوع short مثل int تمام لكنه يحمل قيم أقل (65535 بحد أقصى).

في الكود السابق يتم تحويل الرقم 65536 إلى 0 مجددًا وذلك لأن النوع short لا يمكنه أن يحمل كل هذه القيم لذلك يتم تجاهل الـ bit الأخير، حيث يتم التعبير عن الرقم 65535 في الذاكرة بهذا الشكل

1111 1111 1111 1111

وعندما نقوم بزيادة رقم واحد ليصبح 65536 سوف يكون شكل الرقم كالتالي:

1 0000 0000 0000 0000

ويتم تجاهل الـ bit الأخير (لأن هذا النوع يمكنه أن يحمل 16-bit فقط) مما يؤدي إلى جعل الرقم يصبح صفر في النهاية.

وبالمثل يتم التعامل مع الأرقام السالبقة في المتغيرات التي لا تحمل إشارة unsigned، فعندما تحاول طرح رقمين unsigned وفي حالة كان الرقم سالبًا سوف تبدأ من العكس:
 

#include <iostream>

int main()
{
	unsigned int x{ 3 };
	unsigned int y{ 5 };

	std::cout << x - y << '\n';	// 4294967294
	return 0;
}

وذلك لنفس السبب السابق، وفي الكود الخاص بك يتم التحقق مما إذا كان الرقم أكبر من أو يساوي -1 وبالتالي سوف يتم التعامل مع الرقم -1 الأخير على أنه الرقم 4294967295، ولحل المشكلة يجب تغير الشرط في الدالة check ليصبح أكبر من أو يساوي 0:
 

bool check(int x) {
    return x >= -1;
}

يؤدي إستخدام المتغيرات من نوع unsigned إلى سلوك غير معرف في بعض الأحيان، لذلك لا ينصح أبدًا بإستخدامه إلا عند الضرورة الشديدة مثل برمجة برامج تعمل على أجهزة بذاكرة محدودة للغاية (لتوفير المساحة) أو عند التعامل مع الأرقام الثنائية bitwise operations أو عند التعامل مع خوارزميات التشفير encryption أو توليد الأرقام العشوائية random number generation.

  • 1
نشر

المتغيرات الرقمية الصحيحة في ++C إما أن يكون لها إشارة (موجب - سالب) مثل نمط int أو فقط موجبة unsigned غير مؤشرة..

في النمط unsigned يتم تعريف المتغير على 4 بايت أي 32 بت ولكن يتم أخذ جميع 32 بت لتحمل قيمة  (لايوجد بت إشارة) فيكون مجال القيم لها دوماً موجب.. 

  • من 0 أصغر قيمة
  • حتى 4 مليار و 294 مليون .. 4294967295
0, 1, 2, 3, ... (2^32 = 4294967295)

00000000000000000000000000000000  //0
00000000000000000000000000000001  //1
00000000000000000000000000000010  //2

.
.

11111111111111111111111111111111 // 4294967295

في حال جمع 1 لآخر قيمة 4294967295 + 1 = 0 ستعود ل 0 (لأنه لا يتم الاحتفاظ بالقيمة المحمولة)

في حال طرح 1 من 0 في النمط unsigned،

سيعود التمثيل الثنائي ليصبح 11111111111111111111111111111111 أي أعظم قيمة ممكنة للرقم

يمكن اعتبار التمثيل يدور بشكل حلقة و 0 بجانب 4294967295

*****

في النمط int يتم تعريف المتغير على 4 بايت أي 32 بت منهم 31 تحمل قيمة و الأخير للإشارة، ومجال الأرقام له من:

- 2 ^ 31      => -2147483648

2 ^ 31 - 1    =>  2147483647  (أقل ب 1 لأنها تضمن 0)


كمجال ..

=>>>

-2147483648, -2147483647, -2147483646 ... -2 -1 0 1 2 3 .. 2147483646 , 2147483647, -2147483648, -2147483647

***********

0 0000000000000000000000000000000
^
0 => موجب
1 => سالب

00000000000000000000000000000000  //0
00000000000000000000000000000001  //1
00000000000000000000000000000010  //2

11111111111111111111111111111111  // -1
11111111111111111111111111111110  // -2
11111111111111111111111111111101  // -3

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

*********

في الشيفرة لديك، أنت تقوم بتعريف X ضمن main بنوع unsigned ولكن تستقبلها في الدالة check بالنوع int 

أي تمرير 11111111111111111111111111111111  كمتغير بدون إشارة بقيمة 4294967295 لمتغير بإشارة فسيقوم بمعاملتها ك -1 مما يحقق الشرط، أي X ضمن الدالة check قيمتها -1 بينما في main ستكون 4294967295 وهذا سبب طباعتها.

انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أجب على هذا السؤال...

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.

  • إعلانات

  • تابعنا على



×
×
  • أضف...