อะไรคือความแตกต่างระหว่างmemmoveและmemcpy? คุณมักใช้อันไหนและอย่างไร
อะไรคือความแตกต่างระหว่างmemmoveและmemcpy? คุณมักใช้อันไหนและอย่างไร
คำตอบ:
ด้วยmemcpyปลายทางไม่สามารถทับซ้อนกันของต้นทางได้เลย ด้วยmemmoveสามารถ ซึ่งหมายความว่าmemmoveอาจช้ากว่าเล็กน้อยมากmemcpyเนื่องจากไม่สามารถตั้งสมมติฐานเดียวกันได้
ตัวอย่างเช่นmemcpyอาจคัดลอกที่อยู่จากต่ำไปสูงเสมอ หากปลายทางซ้อนทับกันหลังต้นทางหมายความว่าที่อยู่บางส่วนจะถูกเขียนทับก่อนที่จะคัดลอก memmoveจะตรวจจับสิ่งนี้และคัดลอกในทิศทางอื่น - จากสูงไปต่ำ - ในกรณีนี้ อย่างไรก็ตามการตรวจสอบสิ่งนี้และเปลี่ยนไปใช้อัลกอริทึมอื่น (อาจมีประสิทธิภาพน้อยกว่า) ต้องใช้เวลา
i = i++ + 1ไม่ได้กำหนดไว้ คอมไพเลอร์ไม่ได้ห้ามให้คุณเขียนโค้ดนั้นทุกประการ แต่ผลลัพธ์ของคำสั่งนั้นอาจเป็นอะไรก็ได้และคอมไพเลอร์หรือซีพียูที่แตกต่างกันจะแสดงค่าที่แตกต่างกันที่นี่
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
ความแตกต่างที่สำคัญระหว่างmemmove()และmemcpy()ที่อยู่ในบัฟเฟอร์ - หน่วยความจำชั่วคราว - ถูกนำมาใช้เพื่อให้มีความเสี่ยงของการทับซ้อนกันไม่มี บนมืออื่น ๆ , โดยตรงคัดลอกข้อมูลจากสถานที่ที่ถูกชี้โดยแหล่งที่มาไปยังตำแหน่งที่ชี้โดยปลายทาง ( http://www.cplusplus.com/reference/cstring/memcpy/ )memmove()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
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คัดลอก "front to back" หรือ "back to front" เสมอmemmoveอาจใช้memcpyในกรณีที่ทับซ้อนกัน แต่memcpyอาจคัดลอกด้วยวิธีอื่นขึ้นอยู่กับว่าข้อมูลจะจัดแนวอย่างไรและ / หรือมีข้อมูลเท่าใด คัดลอกดังนั้นแม้ว่าคุณจะทดสอบอย่างไรmemcpyทำสำเนาในระบบของคุณคุณก็ไม่สามารถวางใจได้ว่าผลการทดสอบนั้นจะถูกต้องเสมอไป
หมายความว่าอย่างไรสำหรับคุณเมื่อตัดสินใจว่าจะโทรหาใคร?
ถ้าคุณไม่แน่ใจsrcและdstไม่ทับซ้อนกันให้โทรหาmemmoveเพราะจะนำไปสู่ผลลัพธ์ที่ถูกต้องเสมอและโดยปกติจะเร็วที่สุดเท่าที่จะเป็นไปได้สำหรับกรณีการคัดลอกที่คุณต้องการ
ถ้าคุณรู้แน่นอนsrcและdstไม่ทับซ้อนกันให้โทรหาmemcpyมันจะไม่สำคัญว่าคุณจะเรียกใช้ผลลัพธ์ใดทั้งสองอย่างจะทำงานได้อย่างถูกต้องในกรณีนั้น แต่memmoveจะไม่เร็วไปกว่านี้memcpyและถ้าคุณโชคไม่ดีมันก็อาจจะ ช้ากว่าเพื่อให้คุณชนะการโทรmemcpyเท่านั้น
หนึ่งจัดการกับปลายทางที่ทับซ้อนกัน
จากมาตรฐาน 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()ทำ
มีสองวิธีที่ชัดเจนในการนำไปใช้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 Dr. Jerry Cain (Stanford Intro Systems Lecture - 7) เวลา: 36:00 น.
memcpy() memcopy()