อะไรคือความแตกต่างระหว่าง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()