"إذا أراد العامل أن يؤدي عمله بشكل جيد، فعليه أولاً أن يشحذ أدواته." - كونفوشيوس، "مختارات كونفوشيوس. لو لينجونج"
الصفحة الأمامية > برمجة > الكلمة الرئيسية "المقيدة" الغامضة في لغة C

الكلمة الرئيسية "المقيدة" الغامضة في لغة C

تم النشر بتاريخ 2024-11-04
تصفح:907

The Obscure “restrict” Keyword in C

مقدمة

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

المشكلة

لتوضيح المشكلة التي كان من المفترض حلها، فكر في دالة مثل:

void update_ptrs( int *p, int *q, int const *v ) {
  *p  = *v;
  *q  = *v;
}

والتي سيقوم المترجم بإنشاء رمز x86-64 مثل:

mov eax, [rdx]  ; tmp = *v   // 1
add [rdi], eax  ; *p  = tmp
mov eax, [rdx]  ; tmp = *v   // 3
add [rsi], eax  ; *q  = tmp

قد تتساءل عن سبب إنشاء السطر 3 لأنه يبدو زائدًا عن الحاجة مع السطر 1. المشكلة هي أن المترجم لا يمكنه معرفة أنك لم تفعل شيئًا كهذا:

int x = 1, v = 2;
update_ptrs( &v, &x, &v );   // x = 5, v = 4

في update_ptrs()، p وv سيكونان الاسم المستعار نفس int، لذلك يجب على المترجم تشغيله بشكل آمن ويفترض أن قيمة *v يمكن أن تتغير بين القراءات، ومن هنا تعليمات النقل الإضافية.

بشكل عام، المؤشرات في لغة C تربك عملية التحسين نظرًا لأن المترجم لا يمكنه معرفة ما إذا كان المؤشران يستعاران بعضهما البعض. في التعليمات البرمجية المهمة للأداء، قد يكون حذف قراءات الذاكرة

بمثابة فوز كبير إذا تمكن المترجم من القيام بذلك بأمان. الحل

لحل المشكلة المذكورة أعلاه، تمت إضافة تقييد إلى لغة C للسماح لك بتحديد أن المؤشر المحدد هو المؤشر

الوحيد

لكائن في نطاق المؤشر، أي لا يوجد مؤشر آخر في نفس الأسماء المستعارة للنطاق هو - هي. لاستخدام التقييد، يمكنك إدراجه

بين

* واسم المؤشر في الإعلان. ستكون عملية إعادة كتابة update_ptrs() لاستخدام التقييد هي:
void update_ptrs_v2( int *restrict p, int *restrict q, int const *تقييد v) { *p = *v; *ف = *الخامس; }

void update_ptrs_v2( int *restrict p, int *restrict q,
                     int const *restrict v ) {
  *p  = *v;
  *q  = *v;
}

بإضافة تقييد، يمكن للمترجم الآن إنشاء تعليمات برمجية مثل:


mov eax، [rdx] ; تمب = *الخامس إضافة [ردي]، إياكس؛ * ع = تمب إضافة [رسي]، إياكس؛ *ف = تمب

mov eax, [rdx]  ; tmp = *v
add [rdi], eax  ; *p  = tmp
add [rsi], eax  ; *q  = tmp

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

إذا

لا يتداخل عنوان المصدر والوجهة مع . توجد وظيفة memmove() الأبطأ قليلاً للاستخدام عندما تتداخل العناوين مع . المزالق

يؤدي سوء استخدام التقييد إلى سلوك غير محدد، على سبيل المثال، عن طريق تمرير المؤشرات التي

تفعل

الاسم المستعار لبعضها البعض إلى update_ptrs_v2() أو memcpy(). في بعض الحالات، يمكن للمترجم تحذيرك، ولكن ليس في جميع الحالات، لذلك لا تعتمد على المترجم لرصد سوء الاستخدام. لاحظ أن التقييد مخصص لنطاق معين. يؤدي تعيين مؤشر مقيد إلى

آخر في نفس النطاق

إلى سلوك غير محدد:
باطل f( int *restrict d, int *restrict s ) { int *تقييد p = s; // سلوك غير محدد

void f( int *restrict d, int *restrict s ) {
  int *restrict p = s;    // undefined behavior


باطل f( int *restrict d, int *restrict s ) { int *p = s; // نعم

void f( int *restrict d, int *restrict s ) {
  int *p = s;             // OK

من المقبول أيضًا تعيين مؤشر مقيد في نطاق داخلي إلى مؤشر آخر في نطاق خارجي (ولكن ليس العكس):


باطل f( int *restrict d, int *restrict s ) { { // النطاق الداخلي int *تقييد p = s; // نعم // ... ق = ص؛ // سلوك غير محدد } }

void f( int *restrict d, int *restrict s ) {
  {                       // inner scope
    int *restrict p = s;  // OK
    // ...
    s = p;                // undefined behavior
  }
}

أولاً، يجب عليك بالتأكيد تحديد ملف التعريف الخاص بك (وربما حتى إلقاء نظرة على رمز التجميع الذي تم إنشاؤه) لمعرفة ما إذا كان استخدام التقييد يؤدي بالفعل إلى تحسين أداء

كبير

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

أنت

، فهذا أكثر أمانًا. على سبيل المثال:
باطلة أكثر أمانًا (غير موقعة n) { ن = ن % 2 != 0; // تعادل بالتقريب int *const array = malloc( n * sizeof(unsigned) ); unsigned *restrict half_1st = array; unsigned *restrict half_2nd = array n/2; // ... مجانا (مصفوفة) ؛ }

void safer( unsigned n ) {
  n  = n % 2 != 0;  // make even by rounding up
  int *const array = malloc( n * sizeof(unsigned) );
  unsigned *restrict half_1st = array;
  unsigned *restrict half_2nd = array   n/2;
  // ...
  free( array );
}

ثالثًا، إذا تم استخدام التقييد في معلمات الوظيفة، فمن المحتمل أن يكون

أقل

أمانًا. على سبيل المثال، قم بمقارنة Safer() مع update_ptrs_v2() حيث يتحكم المتصل في المؤشرات. لكل ما تعرفه ، فإن المتصل أخطأ في الأمر وقام بتمرير مؤشرات بهذا الاسم المستعار. متنوع

يمكن فقط تأهيل مؤشرات

للكائنات (أو الفراغ) بتقييد:

تقييد int x; // خطأ: لا يمكن تقييد الكائن كثافة العمليات *p; // خطأ: مؤشر لتقييد الكائن int (*تقييد f)(); // خطأ: مؤشر إلى وظيفة
يمكنك استخدام التقييد لأعضاء البنية، على سبيل المثال:

restrict int x;       // error: can't restrict object
int restrict *p;      // error: pointer to restrict object
int (*restrict f)();  // error: pointer-to-function
عقدة البنية { باطلة *تقييد البيانات؛ عقدة الهيكل *تقييد اليسار؛ عقدة الهيكل *تقييد اليمين؛ };


يقول أن البيانات ستكون المؤشر الوحيد لتلك البيانات وأن اليسار واليمين لن يشيرا أبدًا إلى نفس العقدة. ومع ذلك، فإن استخدام التقييد لأعضاء البنية أمر غير شائع جدًا.

struct node {
   void *restrict data;
   struct node *restrict left;
   struct node *restrict right;
};

قيود. ولم لا؟ هناك إجابة طويلة، ولكن إصدار TL;DR هو:

يمكن أن يكون مصدرًا للأخطاء التي يصعب العثور عليها والتي لم ترغب لجنة C في استيرادها من C.

الاستخدام المتزايد للمؤشرات في لغة C، على سبيل المثال، يجعل استخدام التقييد بأمان أكثر صعوبة.
  • ومع ذلك، العديد من المترجمين لديهم __restrict__ كملحق.
  • خاتمة

في حالات محدودة، يمكن أن يؤدي استخدام التقييد إلى تحسينات في الأداء، ولكن هناك العديد من المخاطر المهمة. إذا كنت تفكر في استخدام التقييد، فقم بتعريف الرمز الخاص بك أولاً.

استخدم بحكمة.

بيان الافراج تم نشر هذه المقالة على: https://dev.to/pauljlucas/the-obscure-restrict-keyword-in-c-2541 إذا كان هناك أي انتهاك، يرجى الاتصال بـ [email protected] لحذفه
أحدث البرنامج التعليمي أكثر>

تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.

Copyright© 2022 湘ICP备2022001581号-3