إضافة تعليق جديد

أشّر عليها ... تنجلي

pointers

المبرمجون العرب (وأنا منهم طبعاً) أصيبوا بداء لا توجد أية إشارات على شفائهم منه في القريب العاجل. هذا المرض اسمه "التعصب البرمجي" وهو يضاف إلى سلسلة طويلة من أنواع "التعصبات الوراثية" التي تحمل عدواها (قبائلنا) العربية من جيل إلى جيل عبر القرون.

كنت أخذت عهداً على نفسي منذ فترة أن لا أتدخل في نقاشات المبرمجين العرب سواء من أصادفهم أثناء عملي او على الانترنت حول لغات البرمجة وكنت انسحب منها بلباقة لمعرفتي بأنها نقاشات عبثية و ستنتهي غالباً ب (زعل) إن لم يكن خلاف شخصي. لكن حدث أن تورطت بالأمس بأحد تلك النقاشات المملة وكان ذلك نتيجة اضطراري للهروب من نقاش في موضوع آخر أكثر (حساسية) فاخترت أهون الشرين. كانت سي شارب C# مستهدفة بالانتقاد من قبل احد متعصبي مدرسة الـ native وركز جل انتقاده على ارتفاع مستوى اللغة و بعدها عن المعالج والذاكرة الامر الذي قد يخذل المبرمج في بعض المواقف التي يحتاج فيها للتعامل بشكل مباشر مع الذاكرة ومؤشراتها. وبما أني أعمل هذه الايام على C# بقدر عملي على دلفي أو ربما أكثر قليلاً فقد حاولت عبثاً أن أشرح له أن الأمر ليس بهذه الصرامة وأن سي شارب على الرغم من أنها تعزل المبرمج عن الذاكرة لكنها ايضاً تتيح له خيار الوصول المباشر للذاكرة عندما يريد (وعلى مسؤليته) مع بعض القيود. والأمر ليس شاقاً كما قد يتخيل بعض المتحاملين على اللغة، كل مافي الأمر أنه يحتاج لمجموعة اجراءات (يمكن اعتبارها اجراءات أمان) بالاضافة لبعض القيود التي ليست موجودة في لغات الـ native . وساحاول هنا أن أشرح باختصار كيفية التعامل مع الذاكرة و المؤشرات في سي شارب مفترضاً ان القارئ لديه معلومات عن المؤشرات في بيئة native راجياً أن يكون في ذلك شيء من الفائدة لمحبي و أيضاً لمنتقدي هذه اللغة.

بداية يجب التنويه إلى أن C# تعامل المؤشرات بطريقة شبيهة بدلفي (نوعاً ما) من حيث أنك طول الوقت تستخدم المؤشرات بشكل عفوي عند استخدامك للمتحولات المرجعية reference variables  والمتحولات المرجعية في C# هي في الحقيقة مؤشرات آمنة -إن صح التعبير-  type-safe pointers فنحن نعرف أن المؤشر ببساطة هو متحول يخزن عنوان لشيء آخر في الذاكرة، الفرق هنا أن C# لا تسمح بالوصول المباشر إلى العنوان المخزن في المتحولات المرجعية وتعامل هذه المتحولات وكانها تخزن المحتوى نفسه الذي تشير إليه.
بما ان السي شارب أو الدوت نت تعزل المبرمج عن الذاكرة (لحمايته) و حماية برامجه من أي احتمال لاقترافه اخطاء في التعامل مع المؤشرات قد تكون مكلفة جداً، لذلك عندما تريد التعامل مع الذاكرة والمؤشرات فبدايةً يجب ان تخبر المترجم compiler بذلك (صراحةً) بأنك وأنت بكامل قواك العقلية تريد أن تتعامل مع الذاكرة بشكل مباشر و أنت مسؤول عن ذلك، وهذا يمكن أن يتم عن طريق اعطاء المترجم بارامتر unsafe اثناء الترجمة من موجه الاوامر:

csc.exe /unsafe Program.cs

او من خلال الخيار Allow unsafe code في خيارات المشروع كما توضح الصورة التالية:

pointers

الكود الذي يتضمن تعاملاً مع الذاكرة أو المؤشرات يجب أن يوسم بعلامة unsafe التي يمكن أن تكون سمة للاجراء markup على الشكل التالي:

unsafe static void doWork()
{
        //…
}

أو سمة لبلوك مغلق من الكود على الشكل التالي:

static void doWork()
{
        unsafe
        {
                // ...
        }
}

بعد أن حددت إجراء أو مقطع كود على أنه unsafe  يمكنك تعريف مؤشرات داخله، و لتعريف مؤشرات نستخدم الاسلوب التالي (مع الانتباه لمكان النجمة):

byte* pData;

حيث هنا عرفنا pData بأنه مؤشر لمتحول من نوع byte وهو كما في حالة مؤشرات native يؤشر لمكان في الذاكرة يحتوي على بايت واحد أو سلسلة بايتات متتابعة.

pointers

تختلف طريقة التعريف عن الطريق المتبعة في C++ بمكان النجمة حيث تأتي النجمة بعد اسم النوع مباشرة بينما في C++ تأتي قبل المتحول، بمعنى في سي شارب تستطيع كتابة التالي:

byte* p1,p2;

لا تسمح سي شارب بتعريف مؤشرات للأنواع المدارة managed types أو التي تحوي أنواع مدارة كأحد حقولها ، فمثلاً في سي شارب لا نستطيع تعريف مؤشر للنوع string:

string* pString;//خطأ مترجم

بالنسبة للمتحولات التي تخزن ضمن المسجل stack تسطيع اسناد قيمة لمؤشرهاstack pointers  باستخدام & (احضر عنوان) وببساطة كما يلي:

int index = 100;
int* p = &index;
*p = 200;

المشكلة التي تبرز في سي شارب أن المتحولات التي تخزن في الكومة heap  - في دوت نت يجب تسميتها managed heap  لأنها كومة خاضعة لادارة مجمع النفاياتgarbage collector   - هي متحولات خاضعة للادارة managed وهي عرضة للنقل (تغيير العنوان في الذاكرة) في أي وقت، لذلك لا تستطيع ببساطة أن تسند قيمة للمؤشر كما يلي:

int[] iArray = { 2, 4, 6 };
int* p = &iArray[0];//خطأ مترجم

هذا السطر سيعطي خطأ أثناء الترجمة. فلكي يشير المؤشر إلى عنوان متحول في الكومة heap  يجب أن نقوم (بتثبيت) هذا المتحول لمنع بيئة الدوت نت من إدارته أو نقله من مكانه في الذاكرة و هذا يتم عن طريق استخدام تعليمة fixed كما يلي:

int[] iArray = { 2, 4, 6 };
fixed (int* p = &iArray[0])
{
        //ضمن هذا البلوك يبقى عنوان المصفوفة ثابتاً في الذاكرة
}

وطبعاً لإرجاع او تغيير القيمة التي يشير إليها المؤشر نستخدم النجمة قبل اسم المؤشر:

*p = 200;

المثال التالي يشمل مجموعة من العمليات على المؤشرات ويوضح أكثر طريقة التعامل معها في C# من خلال التعليقات المكتوبة على الكود.

using System;

namespace PointersDemo
{
        class Program
        {
                static void Main(string[] args)
                {
                        string text = null;
                        sbyte[] bytes = { 0x006D, 0x0079, 0x0020, 0x006C, 0x006F, 0x0076, 0x0065 };
                        unsafe
                        {
                                fixed (sbyte* pData = &bytes[0])
                                {
                                        text = new string(pData);
                                }

                                Console.Write("{0} = ", text);



                                //تعديل محتوى المقطع باستخدام المؤشرات
                                fixed (char* pData = text)
                                {
                                        /*
                                         * PData
                                         * مؤشر محرفي يشير لبداية المقطع
                                         * لكن بما أنه ثابت باستخدام ال fixed
                                         * نسند قيمته لمؤشرات أخرى نستطيع تغيير قيمها
                                         */

                                        byte* p1 = (byte*)pData;//عنوان المحرف الاول لكن باعتبار محتواه بايت
                                        char* p2 = &pData[text.Length-1];//عنوان المحرف الاخير
                                        int sizeOfChar = sizeof(Char);//حجم المحرف في الذاكرة

                                        *p1 = 0x0053;//اسناد بايت جديد لمقطع الذاكرة التي يشير إليها المؤشر
                                       
                                        p1 += sizeOfChar;//القفز لعنوان جديد بمقدار حجم المحرف
                                        *p1 = 0x0079;
                                       
                                        *(p1 += sizeOfChar) = 0x0072;
                                        *(p1 += sizeOfChar) = 0x0069;
                                        *(p1 += sizeOfChar) = 0x0061;
                                       
                                        //  الان لنستخدم المؤشر الثاني و هو مؤشر محرفي
                                        //يشير للمحرف الاخير في المقطع
                                        *p2 = ' ';
                                        *--p2 = ' ';
                                       

                                }
                        }
                        Console.WriteLine(text);

                        Console.ReadKey();
                }
        }
}

أما نتيجة الكود المكتوب فهي ...

pointers

تلك كانت نتيجة الكود ... أما المعنى ففي قلب المبرمج ...


Articles Categories: