الشبكة العربية لمطوري الألعاب

محترف مشرف عبد اللطيف حاجي علي مشاركة 1

استكمالاً للمناقشة التي بدأناها في المشاركة: "مواضيع برمجية محيرة" عن خطأ الـ assert.
http://www.agdn-online.com/communities.aspx?view=posts&threadid=527
 
فأني أقترح أحد الحلين التاليين (أي أن كل منهما حل منفصل يطبق لوحده):
1. أن نقوم بتغيير تعريف MY_ASSERT كما يلي:
#if !NDEBUG
      #define MY_ASSERT(x) if(!x) fail(__FILE__, __LINE__);
#else
      #define MY_ASSERT(x) x;	// Do any side-effects
#endif

2. أن نقوم بتعديل الـ code "المشكلجي" (الذي وجده سلوان في المشاركة الأساسية) كما يلي:
if (*itr++ != 1)
   MY_ASSERT(false)    // Guaranteed fail
 
هل تستطيعون إيجاد المشكلة في كل من هذين الحلين؟ اقترح حلولا أخرى مناسبة.
قد تحتاجون لقراءة الكود الأصلي في المشاركة الأساسية.

عبد اللطيف حاجي علي
مبرمج
In|Framez

خبير  أحمد عبد الغني مشاركة 2

المشكلة في طريقة تعريف الماكرو MY_ASSERT. عندما نستخدم x كما هي، فإننا نعرض أنفسنا للخطأ في حال أدخل المستخدم عبارة مركبة في x.
 
مثلاً:


MY_ASSERT(x == 4);
 
بعد عملية الـ preprocess ستصبح:
 

if (!x == 4) fail("C:\\File.cpp",20);
 
فمعامل النفي ! تم تطبيقه على قيمة x فقط وليس على الشرط ككل.
 
أما في الحالة الثانية فبصراحة لا أعلم ما المشكلة (صعبت السؤال شوي علينا يا أخي 😭  )
 
 
يمكن تعريف الماكرو كالآتي:


#define MY_ASSERT(x) if ((x)) { ... }
 
و:


#define MY_ASSERT(x) (x)
 
جيد؟ 😒

اللهم انصر أهلنا في فلسطين وآجرنا أن نكون عوناً لهم

محترف مشرف عبد اللطيف حاجي علي مشاركة 3

نعم بالفعل. هذا هو أحد الأخطاء الموجودة في تعريف MY_ASSERT. أحب أن أقول هنا شيئاً واحداً فقط: تباً لذاكرتي 🙁 .
في الحقيقة هذا الخطأ كان موجوداً في السؤال الأصلي في "مواضيع برمجية محيرة" وكنت من خلاله أريد أن أنوه إلى هذا الخطأ الشائع (عدم إحاطة المحددات بأقواس) والذي يؤدي إلى الأخطاء التي ذكرتها 😲 . لكن لسبب ما غاب عني هذا الخطأ ونسيته تماماً 😳 . أعتقد أن هذا يدلنا على قاعدة أخرى أساسية: كل ما جاوز التدوين ضاع 😄 .

بالنسبة للخطأ في الاقتراح الأول ولكونه ليس خطأً بالمعنى الحرفي للكلمة، فأنني سأدرج حله هنا ☺ .
المشكلة في هذا التعريف أنه لا يلتزم بما هو معروف عن تحققات الـ assert ; أنها لا يجب أن تؤثر على الأداء في الـ release build. حيث أنه لو كان لدينا السطر التالي في الـ code:
MY_ASSERT(RealySlowCheck() == 42);

فإنه سيترجم إلى هذا السطر في الـ release:
ReallySlowCheck() == 42;

و لن يقوم المترجم بإزالة النداء (الغير ضروري هنا) وبالتالي فإن الأداء سيضيع دون مبرر ☺ . (أتمنى أن تكون قد وضحت الفكرة)

أما بالنسبة للخطأ في الاقتراح الثاني وباعتباره خطأ بالمعنى الحرفي للكلمة فإنني لن أضع حله الكامل 😄 ، على الأقل ليس الآن. لكني أقول لكم إن المشكلة هي مرة أخرى في أن الخرج سيختلف بين الـ release و الـ debug 😲 . لكن مرة أخرى، ما السبب وما الحل؟ 😒


ملاحظة لنفسي كي لا أنسى مرة أخرى 😳 : تذكر أن تضع القواعد العامة لما يصلح ولا يصلح أن يكون تحقق لـ assert 😄

عبد اللطيف حاجي علي
مبرمج
In|Framez

محترف مشرف عبد اللطيف حاجي علي مشاركة 4

حسناً يبدو أن الأسئلة ليست واضحة بما يكفي. لذلك سأضيف وأعدل (وأصحح 😳 ) واضع السؤال كاملاً دون الإشارة إلى مشاركات سابقة
علّ هذا يجعل الأسئلة أوضح ☺ .

للغة: C++
الصعوبة: صعب
المطلوب: السبب واقتراح للحل.
الشرح:
الـ code التالي (قابل للترجمة، إن لم يقم محرك المنتدى بإزالة الأقواس كعادته) يعطي نتائج مختلفة بين الـ release و الـ debug.
#include 
#include 

#if !NDEBUG
	#define MY_ASSERT(x)	if (!(x)) fail(__FILE__, __LINE__);
#else
	#define	MY_ASSERT(x)	// Nothing
#endif

typedef std::vector ByteArray;

void fail(const char *pszFile, int line)
{
	std::cout << "Failure in file" << pszFile <<
           "(" << line << ")" << std::endl;
}

class Image
{
public:
	void DecodeMsg(const ByteArray& arrMsg)
	{
		/* Message Format
		2 bytes: Width (little endian)
		2 bytes: Height (little endian)
		1 byte: Data Order. Always 1 (R then G then B)
		1 bytes: Bits per pixel
		n bytes: Pixles
		*/
		ByteArray::const_iterator itr = arrMsg.begin();

		// 2-bytes witdh
		unsigned char c1 = *itr++, c2 = *itr++;
		m_Width = c1 | (c2 << 8);
		MY_ASSERT(m_Width != 0);

		// 2-bytes height
		c1 = *itr++; c2 = *itr++;
		m_Height = c1 | (c2 << 8);
		MY_ASSERT(m_Height != 0);

		// 1-byte version. Always 1
		if (*itr++ != 1)
			MY_ASSERT(false)	// Guaranteed fail

		// 1-byte BPP
		m_BPP = *itr++;
		MY_ASSERT(m_BPP == 8 || m_BPP == 16 || m_BPP == 24);

		std::cout << "Width: 0x" << std::hex << m_Width << std::endl
				  << "Height: 0x" << m_Height << std::endl
				  << "Bpp: 0x" << m_BPP << std::endl;
		// Continue to read pixels
	}

private:
	unsigned short m_Width, m_Height;
	unsigned int m_BPP;
};


int main()
{
	unsigned char arrBytes[] = { 0x1F, 0x02, 0x3A, 0x00, 0x01, 0x08, 0x65, 0x25};
	ByteArray arrMsg(arrBytes, arrBytes+sizeof(arrBytes));
	Image test;
	test.DecodeMsg(arrMsg);
	return 0;
}

هل يمكنك معرفة السبب؟

عبد اللطيف حاجي علي
مبرمج
In|Framez

خبير  سلوان الهلالي مشاركة 5

همممم... 😲
 

if (*itr++ != 1)
	MY_ASSERT(false)	// Guaranteed fail

// 1-byte BPP
m_BPP = *itr++;
 
عندما يتم إستبدال MY_ASSERT بتعليق في حالة Release, سوف يتم إعتبار سطر الكود التالي للشرط ( m_Bpp...)  هو ناتج الشرط, وبالتالي لن يتم تنفيذه إلا في حالة حدوث خطأ, بينما سيتم تنفيذه دائماً في حالة Debug.
من أين تأتي بهذه المسائل العجيبة... فقط لو أعرف. 😏

خبير  سلوان الهلالي مشاركة 6

لا أعلم في الحقيقة كيف تود أن يكون الحل...
ولكن لدي حل سريع للموضوع هو التأكد من إضافة أقواس {} دائماً حول كل ناتج شرط حتى وإن كان سطر واحد, من الأفضل جعل ذلك عادة دائمة.
سيصبح الكود بهذا الشكل:


if (*itr++ != 1)
{
	MY_ASSERT(false)	// Guaranteed fail
}

// 1-byte BPP
m_BPP = *itr++;
 
من المفترض أن يعمل كل شيء بشكل صحيح الآن.
😋

محترف مشرف عبد اللطيف حاجي علي مشاركة 7

أحسنت أخي سلوان 😄 ...
والحل الذي اقترحته يقوم بالفعل بحل المشكلة. لكن ألا ترى معي أن هذا يضيف عبأ جديداً على المستخدم 😒 ؟
لقد اتفقنا مسبقاً على أن من مسؤولية المستخدم عدم وضع أية تعبيرات لها آثار جانبية داخل قوسي تحقق الـ assert.
فهل يوجد حل يقوم بتغيير MY_ASSERT حتى يكون له نفس التصرف بين الـ debug و الـ release دوماً دون حد حرية المستخدم أكثر من ذلك 😒 ؟

وفي 03 آذار 2009 07:28 م، قال سلوان الهلالي متحمساً:

من أين تأتي بهذه المسائل العجيبة... فقط لو أعرف.
جميع المواضيع التي طرحتها حتى الآن هي أخطاء وقعت فيها شخصياً 😖 . وقضيت الساعات الطوال مع الكثير من الشاي والقهوة حتى اكتشفت سببها وأفضل حلولها. وكنت أتمنى أن أنوه لها حتى أجنب الأعضاء هنا مصاريف المنبهات 😄 .
كنت أنوي أن أضع سؤالين آخرين عن الـ assert ودخاليجها، لكني أعترف أنني "تقلت العيار" 😳 . أعتذر عن ذلك وأعدكم بأن تكون أسئلتي من الآن أكثر منطقية. لذلك سيكون هذا السؤال هو الأخير في  موضوع الـ assert على وعد أن أعود إليه في المستقبل.

على كلٍ لقد أخذت وعداً من إدارة الموقع بوضع تصويت لمعرفة فيما لو كان الأعضاء يريدون أسئلة أسهل أم أصعب أم أن هذا المستوى مناسب.

عبد اللطيف حاجي علي
مبرمج
In|Framez

خبير مدير وسام البهنسي مشاركة 8

في 02 مارس 2009 02:10 م، قال عبد اللطيف حاجي علي بهدوء وتؤدة:

الـ code التالي (قابل للترجمة، إن لم يقم محرك المنتدى بإزالة الأقواس كعادته) يعطي نتائج مختلفة بين الـ release و الـ debug.

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

وسام البهنسي
مبرمج في إنفيديا وإنفريمز