memmove และ memcpy ต่างกันอย่างไร?


คำตอบ:


162

ด้วยmemcpyปลายทางไม่สามารถทับซ้อนกันของต้นทางได้เลย ด้วยmemmoveสามารถ ซึ่งหมายความว่าmemmoveอาจช้ากว่าเล็กน้อยมากmemcpyเนื่องจากไม่สามารถตั้งสมมติฐานเดียวกันได้

ตัวอย่างเช่นmemcpyอาจคัดลอกที่อยู่จากต่ำไปสูงเสมอ หากปลายทางซ้อนทับกันหลังต้นทางหมายความว่าที่อยู่บางส่วนจะถูกเขียนทับก่อนที่จะคัดลอก memmoveจะตรวจจับสิ่งนี้และคัดลอกในทิศทางอื่น - จากสูงไปต่ำ - ในกรณีนี้ อย่างไรก็ตามการตรวจสอบสิ่งนี้และเปลี่ยนไปใช้อัลกอริทึมอื่น (อาจมีประสิทธิภาพน้อยกว่า) ต้องใช้เวลา


1
เมื่อใช้ memcpy ฉันจะรับประกันได้อย่างไรว่าที่อยู่ต้นทางและปลายทางจะไม่ทับซ้อนกัน ฉันควรตรวจสอบเป็นการส่วนตัวว่า src และ dest ไม่ทับซ้อนกัน?
— Alcott

6
@Alcott อย่าใช้ memcpy ถ้าคุณไม่รู้ว่ามันไม่ทับซ้อนกันให้ใช้ memmove แทน เมื่อไม่มีการทับซ้อน memmove และ memcpy จะเทียบเท่ากัน (แม้ว่า memcpy อาจเร็วกว่ามากเล็กน้อยมาก)
— bdonlan

คุณสามารถใช้คำหลัก 'จำกัด ' หากคุณกำลังทำงานกับอาร์เรย์แบบยาวและต้องการปกป้องกระบวนการคัดลอกของคุณ ตัวอย่างเช่นหากวิธีการของคุณใช้เป็นอาร์เรย์อินพุตและเอาต์พุตพารามิเตอร์และคุณต้องตรวจสอบว่าผู้ใช้ไม่ได้ส่งผ่านแอดเดรสเดียวกับอินพุตและเอาต์พุต อ่านเพิ่มเติมที่นี่stackoverflow.com/questions/776283/…
— DanielHsH

10
@DanielHsH 'จำกัด ' คือสัญญาที่คุณสร้างคอมไพเลอร์ คอมไพเลอร์ไม่ได้บังคับใช้ หากคุณใส่ 'จำกัด ' ในอาร์กิวเมนต์ของคุณและในความเป็นจริงแล้วมีการทับซ้อนกัน (หรือโดยทั่วไปแล้วคือการเข้าถึงข้อมูลที่ถูก จำกัด จากตัวชี้ที่ได้มาจากหลาย ๆ ที่) พฤติกรรมของโปรแกรมจะไม่ได้กำหนดไว้จะมีข้อบกพร่องแปลก ๆ เกิดขึ้นและคอมไพเลอร์ โดยปกติจะไม่เตือนคุณเกี่ยวกับเรื่องนี้
— bdonlan

@bdonlan ไม่ใช่แค่สัญญากับคอมไพเลอร์ แต่เป็นข้อกำหนดสำหรับผู้โทรของคุณ เป็นข้อกำหนดที่ไม่ได้บังคับใช้ แต่หากคุณละเมิดข้อกำหนดคุณจะไม่สามารถร้องเรียนได้หากคุณได้รับผลลัพธ์ที่ไม่คาดคิด การละเมิดข้อกำหนดเป็นพฤติกรรมที่ไม่ได้กำหนดเช่นเดียวกับที่i = i++ + 1ไม่ได้กำหนดไว้ คอมไพเลอร์ไม่ได้ห้ามให้คุณเขียนโค้ดนั้นทุกประการ แต่ผลลัพธ์ของคำสั่งนั้นอาจเป็นอะไรก็ได้และคอมไพเลอร์หรือซีพียูที่แตกต่างกันจะแสดงค่าที่แตกต่างกันที่นี่
— Mecki

33

memmoveสามารถจัดการหน่วยความจำที่ทับซ้อนกันmemcpyไม่ได้

พิจารณา

char[] str = "foo-bar";
memcpy(&str[3],&str[4],4); //might blow up

เห็นได้ชัดว่าต้นทางและปลายทางซ้อนทับกันเราเขียนทับ "-bar" ด้วย "bar" เป็นพฤติกรรมที่ไม่ได้กำหนดโดยใช้memcpyหากต้นทางและปลายทางทับซ้อนกันดังนั้นในกรณีนี้เราจำเป็นต้องmemmoveใช้

memmove(&str[3],&str[4],4); //fine

5
ทำไมต้องระเบิดก่อน

4
@ultraman: เนื่องจากอาจมีการใช้งานโดยใช้แอสเซมบลีระดับต่ำที่ต้องการให้หน่วยความจำไม่ทับซ้อนกัน หากเป็นเช่นนั้นคุณสามารถสร้างสัญญาณหรือข้อยกเว้นฮาร์ดแวร์ให้กับโปรเซสเซอร์ที่ยกเลิกแอปพลิเคชัน เอกสารประกอบระบุว่าไม่ได้จัดการกับเงื่อนไข แต่มาตรฐานไม่ได้ระบุว่าจะเกิดอะไรขึ้นเมื่อมีการบรรจุเงื่อนไขเหล่านี้ (ซึ่งเรียกว่าพฤติกรรมที่ไม่ได้กำหนด) พฤติกรรมที่ไม่ได้กำหนดสามารถทำอะไรก็ได้
— Martin York

ด้วย gcc 4.8.2 แม้แต่ memcpy ยังยอมรับตัวชี้ต้นทางและปลายทางที่ทับซ้อนกันและทำงานได้ดี
— GeekyJ

4
@jagsgediya แน่นอนว่ามันอาจจะ. แต่เนื่องจาก memcpy ได้รับการบันทึกไว้ว่าไม่รองรับสิ่งนี้คุณจึงไม่ควรพึ่งพาพฤติกรรมเฉพาะการใช้งานนั้นนั่นคือเหตุผลที่ memmove () มีอยู่ อาจแตกต่างจาก gcc เวอร์ชันอื่น อาจแตกต่างออกไปหาก gcc แทรกใน memcpy แทนที่จะเรียก memcpy () ใน glibc อาจแตกต่างจาก glibc เวอร์ชันเก่าหรือใหม่กว่าเป็นต้น
— เลขที่

จากการปฏิบัติดูเหมือนว่า memcpy และ memmove ก็ทำในสิ่งเดียวกัน พฤติกรรมที่ไม่ได้กำหนดลึกเช่นนี้
— ชีวิต

22

จากเพจmemcpy man.

ฟังก์ชัน memcpy () คัดลอก n ไบต์จาก src พื้นที่หน่วยความจำไปยังพื้นที่หน่วยความจำ dest พื้นที่หน่วยความจำไม่ควรทับซ้อนกัน ใช้memmove (3) หากพื้นที่หน่วยความจำทับซ้อนกัน


12

ความแตกต่างที่สำคัญระหว่างmemmove()และmemcpy()ที่อยู่ในบัฟเฟอร์ - หน่วยความจำชั่วคราว - ถูกนำมาใช้เพื่อให้มีความเสี่ยงของการทับซ้อนกันไม่มี บนมืออื่น ๆ , โดยตรงคัดลอกข้อมูลจากสถานที่ที่ถูกชี้โดยแหล่งที่มาไปยังตำแหน่งที่ชี้โดยปลายทาง ( http://www.cplusplus.com/reference/cstring/memcpy/ )memmove()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

2
แต่ดูเหมือนว่าผลลัพธ์ที่คุณพูดถึงจะกลับกัน !!
— kumar

1
เมื่อฉันเรียกใช้โปรแกรมเดียวกันฉันจะได้ผลลัพธ์ดังนี้ stackoverflow stackstackstw stackstackstw // หมายความว่าไม่มีความแตกต่างในเอาต์พุตระหว่าง memcpy และ memmove
— kumar

4
"คือใน" memmove () "จะใช้บัฟเฟอร์ - หน่วยความจำชั่วคราว -" มันไม่จริง. มันบอกว่า "ราวกับว่า" ดังนั้นมันก็ต้องทำตัวอย่างนั้นไม่ใช่ว่าจะต้องเป็นแบบนั้น นั่นมีความเกี่ยวข้องอย่างแท้จริงเนื่องจากการใช้งาน memmove ส่วนใหญ่เพียงแค่ทำการ XOR-swap
— dhein

2
ฉันไม่คิดว่าการติดตั้งmemmove()จะต้องใช้บัฟเฟอร์ มีสิทธิ์อย่างสมบูรณ์ในการย้ายเข้าที่ (ตราบใดที่การอ่านแต่ละครั้งเสร็จสิ้นก่อนที่จะเขียนไปยังที่อยู่เดียวกัน)
— Toby Speight

12

สมมติว่าคุณต้องใช้ทั้งสองอย่างการติดตั้งอาจมีลักษณะดังนี้:

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คัดลอก "front to back" หรือ "back to front" เสมอmemmoveอาจใช้memcpyในกรณีที่ทับซ้อนกัน แต่memcpyอาจคัดลอกด้วยวิธีอื่นขึ้นอยู่กับว่าข้อมูลจะจัดแนวอย่างไรและ / หรือมีข้อมูลเท่าใด คัดลอกดังนั้นแม้ว่าคุณจะทดสอบอย่างไรmemcpyทำสำเนาในระบบของคุณคุณก็ไม่สามารถวางใจได้ว่าผลการทดสอบนั้นจะถูกต้องเสมอไป

หมายความว่าอย่างไรสำหรับคุณเมื่อตัดสินใจว่าจะโทรหาใคร?

  1. ถ้าคุณไม่แน่ใจsrcและdstไม่ทับซ้อนกันให้โทรหาmemmoveเพราะจะนำไปสู่ผลลัพธ์ที่ถูกต้องเสมอและโดยปกติจะเร็วที่สุดเท่าที่จะเป็นไปได้สำหรับกรณีการคัดลอกที่คุณต้องการ

  2. ถ้าคุณรู้แน่นอนsrcและdstไม่ทับซ้อนกันให้โทรหาmemcpyมันจะไม่สำคัญว่าคุณจะเรียกใช้ผลลัพธ์ใดทั้งสองอย่างจะทำงานได้อย่างถูกต้องในกรณีนั้น แต่memmoveจะไม่เร็วไปกว่านี้memcpyและถ้าคุณโชคไม่ดีมันก็อาจจะ ช้ากว่าเพื่อให้คุณชนะการโทรmemcpyเท่านั้น


+1 เนื่องจาก "ภาพวาด ascii" ของคุณมีประโยชน์ในการทำความเข้าใจว่าเหตุใดจึงไม่มีการทับซ้อนกันโดยไม่ทำให้ข้อมูลเสียหาย
— Scylardor


6

จากมาตรฐาน ISO / IEC: 9899 ที่อธิบายไว้อย่างดี

7.21.2.1 ฟังก์ชัน memcpy

[ ... ]

2 ฟังก์ชัน memcpy จะคัดลอกอักขระ n ตัวจากวัตถุที่ชี้โดย s2 ไปยังวัตถุที่ชี้ด้วย s1 หากการคัดลอกเกิดขึ้นระหว่างวัตถุที่ทับซ้อนกันพฤติกรรมนั้นจะไม่ได้กำหนดไว้

และ

7.21.2.2 ฟังก์ชัน memmove

[ ... ]

2 ฟังก์ชัน memmove จะคัดลอกอักขระ n ตัวจากวัตถุที่ชี้โดย s2 ไปยังวัตถุที่ชี้ไปด้วย s1 การคัดลอกจะเกิดขึ้นราวกับว่าอักขระ n จากวัตถุที่ชี้โดย s2 ถูกคัดลอกไปยังอาร์เรย์ชั่วคราวของอักขระ n ที่ไม่ทับซ้อนกันของวัตถุที่ชี้ไปด้วย s1 และ s2 จากนั้นอักขระ n จากอาร์เรย์ชั่วคราวจะถูกคัดลอกไปยัง วัตถุที่ชี้โดย s1

ฉันมักจะใช้อันไหนตามคำถามขึ้นอยู่กับฟังก์ชันที่ฉันต้องการ

ในข้อความธรรมดาmemcpy()ไม่อนุญาตs1และs2ทับซ้อนกันในขณะที่memmove()ทำ


0

มีสองวิธีที่ชัดเจนในการนำไปใช้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ไปยังตัวชี้คอนกรีตบางประเภทแล้วพวกเขาก็ไม่ชี้ไปที่วัตถุเดียวกัน (โดยทั่วไป) จึงไม่สามารถเปรียบเทียบได้ แต่ไลบรารีมาตรฐานสามารถมีความรู้เกี่ยวกับแพลตฟอร์มเพียงพอที่จะทำการเปรียบเทียบดังกล่าวโดยไม่ก่อให้เกิดพฤติกรรมที่ไม่ได้กำหนด


โปรดทราบว่าในชีวิตจริงการนำไปใช้งานมักจะซับซ้อนกว่าอย่างเห็นได้ชัดเพื่อให้ได้ประสิทธิภาพสูงสุดจากการถ่ายโอนขนาดใหญ่ (เมื่ออนุญาตให้จัดตำแหน่ง) และ / หรือการใช้แคชข้อมูลที่ดี โค้ดด้านบนเป็นเพียงการกำหนดประเด็นให้ง่ายที่สุด


0

memmove สามารถจัดการกับภูมิภาคต้นทางและปลายทางที่ทับซ้อนกันได้ในขณะที่ memcpy ไม่สามารถทำได้ ในสองอย่างนี้ memcpy มีประสิทธิภาพมากกว่า ดังนั้นควรใช้ memcpy ดีกว่าถ้าทำได้

อ้างอิง: https://www.youtube.com/watch?v=Yr1YnOVG-4g Dr. Jerry Cain (Stanford Intro Systems Lecture - 7) เวลา: 36:00 น.


คำตอบนี้บอกว่า "อาจจะเร็วกว่านี้" และให้ข้อมูลเชิงปริมาณที่ระบุความแตกต่างเพียงเล็กน้อย คำตอบนี้ยืนยันอย่างหนึ่งคือ "มีประสิทธิภาพมากกว่า" คุณพบว่าสิ่งที่เร็วกว่านั้นมีประสิทธิภาพมากแค่ไหน? BTW: ผมถือว่าคุณหมายถึงและไม่ได้memcpy() memcopy()
— chux - คืนสถานะ Monica

ความคิดเห็นนี้สร้างขึ้นจากการบรรยายของ Dr. Jerry Cain ฉันจะขอให้คุณฟังการบรรยายของเขาในเวลา 36:00 น. เพียง 2-3 นาทีก็เพียงพอแล้ว และขอบคุณสำหรับการจับ : D
— Ehsan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.