من بين أشياء أخرى، أضاف 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 تربك عملية التحسين نظرًا لأن المترجم لا يمكنه معرفة ما إذا كان المؤشران يستعاران بعضهما البعض. في التعليمات البرمجية المهمة للأداء، قد يكون حذف قراءات الذاكرةبمثابة فوز كبير إذا تمكن المترجم من القيام بذلك بأمان. الحل
لكائن في نطاق المؤشر، أي لا يوجد مؤشر آخر في نفس الأسماء المستعارة للنطاق هو - هي. لاستخدام التقييد، يمكنك إدراجه
بين * واسم المؤشر في الإعلان. ستكون عملية إعادة كتابة 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، على سبيل المثال، يجعل استخدام التقييد بأمان أكثر صعوبة.في حالات محدودة، يمكن أن يؤدي استخدام التقييد إلى تحسينات في الأداء، ولكن هناك العديد من المخاطر المهمة. إذا كنت تفكر في استخدام التقييد، فقم بتعريف الرمز الخاص بك أولاً.
تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.
Copyright© 2022 湘ICP备2022001581号-3