اقتباس من وثائق مجلس التعاون الخليجي [1]:تُستخدم سمة التنظيف لتشغيل دالة عندما يخرج متغير عن نطاقه. لا يمكن تطبيق هذه السمة إلا على المتغيرات التلقائية ، ولا يمكن استخدامها مع المعلمات أو المتغيرات الثابتة. يجب أن تأخذ الدالة معلمة واحدة ، مؤشر إلى نوع متوافق مع المتغير. يتم تجاهل القيمة المرجعة للدالة ، إن وجدت.
إذا تم تمكين خيار -fexceptions ، فسيتم تشغيل وظيفة cleanup_function عندما يتم فك المكدس ، أثناء معالجة الاستثناء. لاحظ أن سمة التنظيف لا تلتقط الاستثناءات ؛ فهي تقوم بإجراء فقط. إذا لم تُرجع الدالة cleanup_function بشكل طبيعي ، فسيكون السلوك غير محدد.
يتم دعم سمة التنظيف من قبل جامعي gcc و clang.في هذه المقالة سأصف الخيارات المختلفة للاستخدام العملي لخاصية التنظيف وأخذ بعين الاعتبار الهيكل الداخلي للمكتبة ، والذي يستخدم التنظيف لتنفيذ نظائر std :: unique_ptr و std :: shared_ptr في C.دعنا نحاول تنظيف تخصيص الذاكرة:#include<stdlib.h>
#include<stdio.h>
static void free_int(int **ptr)
{
free(*ptr);
printf("cleanup done\n");
}
int main()
{
__attribute__((cleanup(free_int))) int *ptr_one = (int *)malloc(sizeof(int));
return 0;
}
نبدأ ، يقوم البرنامج بطباعة "تنظيف". كل شيء يعمل ، هتاف.لكن هناك عيب واحد يتضح على الفور: لا يمكننا ببساطة الكتابة__attribute__((cleanup(free_int)))
لأن الوظيفة التي تسمى بسمة التنظيف يجب أن تأخذ مؤشرًا إلى المتغير المحرر كوسيطة ، ولدينا مؤشر إلى منطقة الذاكرة المخصصة ، أي أننا بحاجة بالتأكيد إلى وظيفة تأخذ مؤشرًا مزدوجًا. للقيام بذلك ، نحتاج إلى وظيفة غلاف إضافية:static void free_int(int **ptr)
{
free(*ptr);
...
}
بالإضافة إلى ذلك ، لا يمكننا استخدام الدالة العالمية لتحرير أي متغيرات ، لأنها تتطلب أنواعًا مختلفة من الحجج. لذلك ، نعيد كتابة الوظيفة على النحو التالي:static void _free(void *p) {
free(*(void**) p);
printf("cleanup done\n");
}
الآن يمكنها قبول أي مؤشرات.فيما يلي ماكرو مفيد آخر (من قاعدة رمز systemd ):#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \
static inline void func##p(type *p) { \
if (*p) \
func(*p); \
} \
struct __useless_struct_to_allow_trailing_semicolon__
والتي يمكن استخدامها لاحقًا على هذا النحو:DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, pclose);
#define _cleanup_pclose_ __attribute__((cleanup(pclosep)))
لكن هذا ليس كل شيء. توجد مكتبة تنفذ نظائرها من الإيجابيات unique_ptr و Shared_ptr باستخدام هذه السمة: https://github.com/Snaipe/libcsptrمثال للاستخدام (مأخوذ من [2]):#include <stdio.h>
#include <csptr/smart_ptr.h>
#include <csptr/array.h>
void print_int(void *ptr, void *meta) {
(void) meta;
printf("%d\n", *(int*) ptr);
}
int main(void) {
smart int *ints = unique_ptr(int[5], {5, 4, 3, 2, 1}, print_int);
for (size_t i = 0; i < array_length(ints); ++i) {
ints[i] = i + 1;
}
return 0;
}
كل شيء يعمل بشكل رائع!ودعنا نرى ما بداخل هذا السحر. لنبدأ بـ unique_ptr (و Shared_ptr في نفس الوقت):# define shared_ptr(Type, ...) smart_ptr(SHARED, Type, __VA_ARGS__)
# define unique_ptr(Type, ...) smart_ptr(UNIQUE, Type, __VA_ARGS__)
دعونا نمضي قدما ونرى مدى عمق حفرة الأرنب:# define smart_arr(Kind, Type, Length, ...) \
({ \
struct s_tmp { \
CSPTR_SENTINEL_DEC \
__typeof__(__typeof__(Type)[Length]) value; \
f_destructor dtor; \
struct { \
const void *ptr; \
size_t size; \
} meta; \
} args = { \
CSPTR_SENTINEL \
__VA_ARGS__ \
}; \
void *var = smalloc(sizeof (Type), Length, Kind, ARGS_); \
if (var != NULL) \
memcpy(var, &args.value, sizeof (Type)); \
var; \
})
حتى الآن ، لم يزد الوضوح ، قبل أن يكون لدينا خليط من وحدات الماكرو في أفضل تقاليد هذه اللغة. لكننا لسنا معتادين على التراجع. كشف التشابك:define CSPTR_SENTINEL .sentinel_ = 0,
define CSPTR_SENTINEL_DEC int sentinel_;
...
typedef void (*f_destructor)(void *, void *);
نفذ الاستبدال:# define smart_arr(Kind, Type, Length, ...) \
({ \
struct s_tmp { \
int sentinel_; \
__typeof__(__typeof__(Type)[Length]) value; \
void (*)(void *, void *) dtor; \
struct { \
const void *ptr; \
size_t size; \
} meta; \
} args = { \
.sentinel_ = 0, \
__VA_ARGS__ \
}; \
void *var = smalloc(sizeof (Type), Length, Kind, ARGS_); \
if (var != NULL) \
memcpy(var, &args.value, sizeof (Type)); \
var; \
})
ومحاولة فهم ما يحدث هنا. لدينا بنية معينة ، تتكون من متغير sentinel_ ، صفيف معين (النوع) [الطول] ، مؤشر إلى وظيفة مدمرة ، والتي يتم تمريرها في الجزء الإضافي (...) من الوسائط الماكرو ، وهيكل ميتا ، والذي يتم ملؤه أيضًا بوسيطات إضافية. التالي هو مكالمةsmalloc(sizeof (Type), Length, Kind, ARGS_);
ما هو سمولوك؟ نجد المزيد من سحر القوالب (لقد قمت بالفعل ببعض الاستبدالات هنا):enum pointer_kind {
UNIQUE,
SHARED,
ARRAY = 1 << 8
};
typedef struct {
CSPTR_SENTINEL_DEC
size_t size;
size_t nmemb;
enum pointer_kind kind;
f_destructor dtor;
struct {
const void *data;
size_t size;
} meta;
} s_smalloc_args;
__attribute__ ((malloc)) void *smalloc(s_smalloc_args *args);
# define smalloc(...) \
smalloc(&(s_smalloc_args) { CSPTR_SENTINEL __VA_ARGS__ })
حسنًا ، لهذا السبب نحب C. هناك أيضًا وثائق في المكتبة ( أيها الأولياء ، أوصي الجميع بأخذ مثال منهم): تستدعيالدالة smalloc () المُخصص (malloc (3) افتراضيًا) ، المؤشر الذي تم إرجاعه هو مؤشر "ذكي". <...> إذا كان الحجم 0 ، يتم إرجاع NULL. إذا كان nmemb يساوي 0 ، فسوف يعيد smalloc مؤشرًا ذكيًا إلى كتلة ذاكرة بحجم بايت على الأقل ، ومؤشر عددي ذكي ، إذا لم يكن nmemb يساوي 0 ، فسيتم إرجاع مؤشر إلى كتلة ذاكرة بحجم على الأقل * nmemb ، وكان المؤشر من نوع الصفيف.أصلي«The smalloc() function calls an allocator (malloc (3) by default), such that the returned pointer is a smart pointer. <...> If size is 0, then smalloc() returns NULL. If nmemb is 0, then smalloc shall return a smart pointer to a memory block of at least size bytes, and the smart pointer is a scalar. Otherwise, it shall return a memory block to at least size * nmemb bytes, and the smart pointer is an array.»
هنا مصدر Smalloc:__attribute__ ((malloc)) void *smalloc(s_smalloc_args *args) {
return (args->nmemb == 0 ? smalloc_impl : smalloc_array)(args);
}
دعونا نلقي نظرة على الكود smalloc_impl ، الذي يخصص كائنات من الأنواع العددية. لتقليل حجم الصوت ، قمت بحذف الرمز المرتبط بالمؤشرات المشتركة وقمت باستبدال ماكرو وماكرو:static void *smalloc_impl(s_smalloc_args *args) {
if (!args->size)
return NULL;
size_t aligned_metasize = align(args->meta.size);
size_t size = align(args->size);
size_t head_size = sizeof (s_meta);
s_meta_shared *ptr = malloc(head_size + size + aligned_metasize + sizeof (size_t));
if (ptr == NULL)
return NULL;
char *shifted = (char *) ptr + head_size;
if (args->meta.size && args->meta.data)
memcpy(shifted, args->meta.data, args->meta.size);
size_t *sz = (size_t *) (shifted + aligned_metasize);
*sz = head_size + aligned_metasize;
*(s_meta*) ptr = (s_meta) {
.kind = args->kind,
.dtor = args->dtor,
.ptr = sz + 1
};
return sz + 1;
}
هنا نرى أن ذاكرة المتغير يتم تخصيصها ، بالإضافة إلى رأس معين من النوع s_meta بالإضافة إلى مساحة البيانات الوصفية لحجم args-> meta.size محاذاة مع حجم الكلمة ، بالإضافة إلى كلمة أخرى (sizeof (size_t)). تقوم الدالة بإرجاع مؤشر إلى منطقة ذاكرة المتغير: ptr + head_size + align_metasize + 1.دعنا نخصص متغيرًا من النوع int ، مُهيأًا بالقيمة 42:smart void *ptr = unique_ptr(int, 42);
هنا ذكي ماكرو:# define smart __attribute__ ((cleanup(sfree_stack)))
عندما يترك المؤشر النطاق ، يسمى sfree_stack:CSPTR_INLINE void sfree_stack(void *ptr) {
union {
void **real_ptr;
void *ptr;
} conv;
conv.ptr = ptr;
sfree(*conv.real_ptr);
*conv.real_ptr = NULL;
}
دالة Sfree (مختصر):void sfree(void *ptr) {
s_meta *meta = get_meta(ptr);
dealloc_entry(meta, ptr);
}
تقوم دالة dealloc_entry ، بشكل أساسي ، باستدعاء أداة إتلاف مخصصة إذا حددناها في وسائط unique_ptr ، وتم تخزين المؤشر إليها في بيانات التعريف. إذا لم يكن الأمر كذلك ، يتم فقط تنفيذ (meta) مجانًا.قائمة المصادر:[1] السمات المتغيرة المشتركة .[2] طريقة جيدة واصطلاحية لاستخدام GCC و clang __attribute __ ((تنظيف)) وإعلانات المؤشر .[3] استخدام سمة المتغير __cleanup__ في دول مجلس التعاون الخليجي .