سؤال

ماهو الفرق بين 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

memcpy الصفحة رجل.

<اقتباس فقرة>   

ووmemcpy () نسخ وظيفة ن بايت   من ناحية الذاكرة SRC إلى منطقة الذاكرة   دست. ينبغي مناطق الذاكرة لا   تداخل. استخدام memmove (3) إذا كانت الذاكرة   مناطق لا تتداخل.

الفرق الرئيسي بين memmove() و memcpy() هل هذا في memmove() أ متعادل - الذاكرة المؤقتة - مستخدمة، لذلك لا يوجد خطر التداخل.على الجانب الآخر، memcpy() نسخ البيانات مباشرة من الموقع المشار إليه بواسطة مصدر إلى المكان الذي أشار إليه وجهة. (http://www.cplusplus.com/reference/cstring/memcpy/)

خذ بعين الاعتبار الأمثلة التالية:

  1. #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
    
  2. ولكن في هذا المثال، لن تكون النتائج هي نفسها:

    #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 النسخ الموجودة على نظامك، فلا يمكنك الاعتماد على نتيجة الاختبار هذه لتكون صحيحة دائمًا.

ماذا يعني ذلك بالنسبة لك عندما تقرر أي واحد ستتصل؟

  1. إلا إذا كنت تعرف ذلك على وجه اليقين src و dst لا تتداخل، اتصل memmove لأنه سيؤدي دائمًا إلى نتائج صحيحة وعادةً ما يكون ذلك بالسرعة الممكنة لحالة النسخ التي تحتاجها.

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

هناك طريقتان واضحتان للتنفيذ mempcpy(void *dest, const void *src, size_t n) (تجاهل قيمة الإرجاع):

  1. for (char *p=src, *q=dest;  n-->0;  ++p, ++q)
        *q=*p;
    
  2. 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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top