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.