ما هو الفرق بين memmove و memcpy؟
سؤال
ماهو الفرق بين memmove
و memcpy
؟أي واحد تستخدمه عادة وكيف؟
المحلول
ومع memcpy
، الوجهة لا يمكن أن تتداخل مصدر على الإطلاق. مع memmove
ما في وسعها. وهذا يعني أن memmove
قد يكون أبطأ قليلا جدا من memcpy
، لأنه لا يمكن أن تجعل من نفس الافتراضات.
وعلى سبيل المثال، قد memcpy
دائما نسخ عناوين من الأقل إلى الأعلى. إذا تتداخل الوجهة بعد المصدر، وهذا يعني أنه سيتم الكتابة فوق بعض العناوين قبل نسخها. سوف memmove
الكشف عن هذا ونسخ في الاتجاه الآخر - من الأعلى إلى الأقل - في هذه الحالة. ومع ذلك، والتحقق من ذلك، والتحول إلى أخرى (وربما أقل كفاءة) خوارزمية يستغرق وقتا طويلا.
نصائح أخرى
وmemmove
يمكن التعامل مع ذاكرة متداخلة، memcpy
لا يمكن.
والنظر
char[] str = "foo-bar";
memcpy(&str[3],&str[4],4); //might blow up
من الواضح أن المصدر والوجهة تتداخل الآن، نحن الكتابة
"-bar" مع "شريط". انها سلوك غير معرف باستخدام memcpy
إذا كان مصدر
والتداخل جهة حتى في هذه الحالات الحالة نحن بحاجة memmove
.
memmove(&str[3],&str[4],4); //fine
الفرق الرئيسي بين memmove()
و memcpy()
هل هذا في memmove()
أ متعادل - الذاكرة المؤقتة - مستخدمة، لذلك لا يوجد خطر التداخل.على الجانب الآخر، memcpy()
نسخ البيانات مباشرة من الموقع المشار إليه بواسطة مصدر إلى المكان الذي أشار إليه وجهة. (http://www.cplusplus.com/reference/cstring/memcpy/)
خذ بعين الاعتبار الأمثلة التالية:
#include <stdio.h> #include <string.h> int main (void) { char string [] = "stackoverflow"; char *first, *second; first = string; second = string; puts(string); memcpy(first+5, first, 5); puts(first); memmove(second+5, second, 5); puts(second); return 0; }
كما توقعت، سيتم طباعة هذا:
stackoverflow stackstacklow stackstacklow
ولكن في هذا المثال، لن تكون النتائج هي نفسها:
#include <stdio.h> #include <string.h> int main (void) { char string [] = "stackoverflow"; char *third, *fourth; third = string; fourth = string; puts(string); memcpy(third+5, third, 7); puts(third); memmove(fourth+5, fourth, 7); puts(fourth); return 0; }
انتاج:
stackoverflow stackstackovw stackstackstw
وذلك لأن "memcpy()" يقوم بما يلي:
1. stackoverflow
2. stacksverflow
3. stacksterflow
4. stackstarflow
5. stackstacflow
6. stackstacklow
7. stackstacksow
8. stackstackstw
واحد يعالج وجهات متداخلة الآخر لا.
ببساطة من معيار ISO/IEC:9899 تم وصفه بشكل جيد.
7.21.2.1 الدالة memcpy
[...]
2 تقوم دالة memcpy بنسخ أحرف n من الكائن الذي أشار إليه بواسطة S2 إلى الكائن الذي يشير إليه S1. إذا حدث النسخ بين كائنات متداخلة، يكون السلوك غير محدد.
و
7.21.2.2 وظيفة المذكرة
[...]
2 تقوم وظيفة Memmove بنسخ أحرف N من الكائن الذي أشار إليه S2 إلى الكائن الذي يشير إليه S1.يتم النسخ كما لو أن أحرف N من الكائن الذي يشير إليه S2 يتم نسخه لأول مرة في مجموعة مؤقتة من الأحرف N التي لا تتداخل يتم توجيه الكائنات التي أشار إليها S1 و S2 ، ثم يتم نسخ الأحرف N من الصفيف المؤقت إلى الكائن الذي تم الإشارة إليه بواسطة S1.
يعتمد الخيار الذي أستخدمه عادةً وفقًا للسؤال على الوظيفة التي أحتاجها.
في نص عادي memcpy()
لا يسمح s1
و s2
للتداخل، في حين memmove()
يفعل.
بافتراض أنه سيتعين عليك تنفيذ كليهما، يمكن أن يبدو التنفيذ كما يلي:
void memmove ( void * dst, const void * src, size_t count ) {
if ((uintptr_t)src < (uintptr_t)dst) {
// Copy from back to front
} else if ((uintptr_t)dst < (uintptr_t)src) {
// Copy from front to back
}
}
void mempy ( void * dst, const void * src, size_t count ) {
if ((uintptr_t)src != (uintptr_t)dst) {
// Copy in any way you want
}
}
وهذا يجب أن يفسر الفرق جيدًا. memmove
ينسخ دائمًا بطريقة تجعله آمنًا إذا src
و dst
تتداخل، في حين memcpy
فقط لا يهتم كما تقول الوثائق عند الاستخدام memcpy
, منطقتي الذاكرة لا يجب تداخل.
على سبيل المثاللو memcpy
نسخ "من الأمام إلى الخلف" ويتم محاذاة كتل الذاكرة على هذا النحو
[---- src ----]
[---- dst ---]
نسخ البايت الأول من src
ل dst
يدمر بالفعل محتوى البايتات الأخيرة من src
قبل أن يتم نسخ هذه.فقط النسخ "من الخلف إلى الأمام" سيؤدي إلى نتائج صحيحة.
الآن مبادلة src
و dst
:
[---- dst ----]
[---- src ---]
في هذه الحالة، يكون النسخ "من الأمام إلى الخلف" آمنًا فقط لأن النسخ "من الخلف إلى الأمام" سيؤدي إلى التدمير src
بالقرب من مقدمتها بالفعل عند نسخ البايت الأول.
ربما لاحظتم أن memmove
التنفيذ أعلاه لا يختبر حتى ما إذا كان هناك تداخل بالفعل، بل يتحقق فقط من مواضعها النسبية، ولكن هذا وحده سيجعل النسخة آمنة.مثل memcpy
يستخدم عادةً أسرع طريقة ممكنة لنسخ الذاكرة على أي نظام، memmove
عادة ما يتم تنفيذه على النحو التالي:
void memmove ( void * dst, const void * src, size_t count ) {
if ((uintptr_t)src < (uintptr_t)dst
&& (uintptr_t)src + count > (uintptr_t)dst
) {
// Copy from back to front
} else if ((uintptr_t)dst < (uintptr_t)src
&& (uintptr_t)dst + count > (uintptr_t)src
) {
// Copy from front to back
} else {
// They don't overlap for sure
memcpy(dst, src, count);
}
}
في بعض الأحيان، إذا memcpy
ينسخ دائمًا "من الأمام إلى الخلف" أو من "من الخلف إلى الأمام"، memmove
قد تستخدم أيضا memcpy
في إحدى الحالات المتداخلة ولكن memcpy
قد يتم نسخها بطريقة مختلفة اعتمادًا على كيفية محاذاة البيانات و/أو كمية البيانات المطلوب نسخها، لذلك حتى لو اختبرت كيفية memcpy
النسخ الموجودة على نظامك، فلا يمكنك الاعتماد على نتيجة الاختبار هذه لتكون صحيحة دائمًا.
ماذا يعني ذلك بالنسبة لك عندما تقرر أي واحد ستتصل؟
إلا إذا كنت تعرف ذلك على وجه اليقين
src
وdst
لا تتداخل، اتصلmemmove
لأنه سيؤدي دائمًا إلى نتائج صحيحة وعادةً ما يكون ذلك بالسرعة الممكنة لحالة النسخ التي تحتاجها.إذا كنت تعرف على وجه اليقين ذلك
src
وdst
لا تتداخل، اتصلmemcpy
لأنه لن يهم أيهما ستتصل للحصول على النتيجة، فكلاهما سيعمل بشكل صحيح في هذه الحالة، ولكنmemmove
لن يكون أسرع منmemcpy
وإذا لم تكن محظوظًا، فقد يكون الأمر أبطأ، لذا يمكنك فقط الفوز بالمكالمةmemcpy
.
هناك طريقتان واضحتان للتنفيذ mempcpy(void *dest, const void *src, size_t n)
(تجاهل قيمة الإرجاع):
for (char *p=src, *q=dest; n-->0; ++p, ++q) *q=*p;
char *p=src, *q=dest; while (n-->0) q[n]=p[n];
في التنفيذ الأول، تنتقل النسخة من العناوين المنخفضة إلى العناوين العالية، وفي الثانية، من الأعلى إلى الأدنى.إذا كان النطاق المراد نسخه يتداخل (كما هو الحال عند تمرير مخزن إطارات مؤقت، على سبيل المثال)، فسيكون اتجاه واحد فقط من العملية صحيحًا، وسيقوم الاتجاه الآخر بالكتابة فوق المواقع التي سيتم القراءة منها لاحقًا.
أ memmove()
التنفيذ، في أبسط صوره، سيتم اختباره dest<src
(بطريقة ما تعتمد على النظام الأساسي)، وتنفيذ الاتجاه المناسب لـ memcpy()
.
لا يمكن لرمز المستخدم فعل ذلك بالطبع، لأنه حتى بعد الإرسال src
و dst
بالنسبة لبعض أنواع المؤشرات الملموسة، فهي لا تشير (بشكل عام) إلى نفس الكائن وبالتالي لا يمكن مقارنتها.لكن يمكن أن تتمتع المكتبة القياسية بمعرفة كافية بالنظام الأساسي لإجراء مثل هذه المقارنة دون التسبب في سلوك غير محدد.
لاحظ أنه في الحياة الواقعية، تميل عمليات التنفيذ إلى أن تكون أكثر تعقيدًا بشكل ملحوظ، للحصول على أقصى قدر من الأداء من عمليات النقل الأكبر (عندما تسمح المحاذاة) و/أو الاستخدام الجيد لذاكرة التخزين المؤقت للبيانات.الكود أعلاه هو فقط لتوضيح النقطة قدر الإمكان.
وmemmove يمكن التعامل مع تداخل مناطق المصدر والمقصد، في حين memcpy لا يمكن. بين اثنين، memcpy هي أكثر كفاءة. لذلك، من الأفضل استخدام memcpy أنه إذا كنت تستطيع.
والمرجعي: https://www.youtube.com/watch؟v=Yr1YnOVG -4g الدكتور جيري قابيل، (ستانفورد مقدمة نظم محاضرة - 7) الوقت: 36:00