Câu trả lời:
Với memcpy, đích không được trùng với nguồn. Với memmovenó có thể. Điều này có nghĩa là memmovecó thể chậm hơn một chút so vớimemcpy vì nó không thể đưa ra các giả định tương tự.
Ví dụ: memcpycó thể luôn sao chép địa chỉ từ thấp đến cao. Nếu đích trùng lặp sau nguồn, điều này có nghĩa là một số địa chỉ sẽ bị ghi đè trước khi sao chép. memmovesẽ phát hiện điều này và sao chép theo hướng khác - từ cao xuống thấp - trong trường hợp này. Tuy nhiên, việc kiểm tra điều này và chuyển sang thuật toán khác (có thể kém hiệu quả hơn) cần nhiều thời gian.
i = i++ + 1là không xác định; trình biên dịch không cấm bạn viết chính xác mã đó nhưng kết quả của lệnh đó có thể là bất kỳ thứ gì và các trình biên dịch hoặc CPU khác nhau sẽ hiển thị các giá trị khác nhau ở đây.
                    memmovecó thể xử lý bộ nhớ chồng chéo, memcpykhông thể.
Xem xét
char[] str = "foo-bar";
memcpy(&str[3],&str[4],4); //might blow up
Rõ ràng nguồn và đích bây giờ trùng nhau, chúng tôi đang ghi đè "-bar" bằng "bar". Đó là hành vi không xác định bằng cách sử dụng memcpynếu nguồn và đích trùng nhau, vì vậy trong trường hợp này, chúng tôi cần memmove.
memmove(&str[3],&str[4],4); //fine
              Sự khác biệt chính giữa memmove()và memcpy()là trong memmove()một bộ đệm - bộ nhớ tạm thời - được sử dụng, vì vậy không có nguy cơ chồng chéo. Mặt khác, memcpy()sao chép trực tiếp dữ liệu từ vị trí được nguồn trỏ đến vị trí được trỏ bởi đích . ( http://www.cplusplus.com/reference/cstring/memcpy/ )
Hãy xem xét các ví dụ sau:
#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;
}
Như bạn mong đợi, điều này sẽ in ra:
stackoverflow
stackstacklow
stackstacklowNhưng trong ví dụ này, kết quả sẽ không giống nhau:
#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;
}
Đầu ra:
stackoverflow
stackstackovw
stackstackstwĐó là bởi vì "memcpy ()" thực hiện như sau:
1.  stackoverflow
2.  stacksverflow
3.  stacksterflow
4.  stackstarflow
5.  stackstacflow
6.  stackstacklow
7.  stackstacksow
8.  stackstackstw
              memmove()bắt buộc phải sử dụng bộ đệm. Nó hoàn toàn có quyền di chuyển tại chỗ (miễn là mỗi lần đọc hoàn thành trước khi ghi vào cùng một địa chỉ).
                    Giả sử bạn sẽ phải triển khai cả hai, việc triển khai có thể trông như thế:
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
    }
}
Và điều này sẽ giải thích khá rõ sự khác biệt. memmoveluôn sao chép theo cách mà nó vẫn an toàn nếu srcvà dstchồng chéo lên nhau, trong khi memcpykhông quan tâm như tài liệu hướng dẫn khi sử dụng memcpy, hai vùng bộ nhớ không được chồng lên nhau.
Ví dụ: nếu memcpybản sao "từ trước ra sau" và các khối bộ nhớ được căn chỉnh như thế này
[---- src ----]
            [---- dst ---]
sao chép byte đầu tiên của srcđể dstđã phá hủy nội dung của byte cuối cùng củasrc trước khi chúng được sao chép. Chỉ sao chép "từ trước ra trước" mới dẫn đến kết quả chính xác.
Bây giờ hoán đổi srcvà dst:
[---- dst ----]
            [---- src ---]
Trong trường hợp đó, chỉ an toàn khi sao chép "từ trước ra sau" vì sao chép "từ sau ra trước" sẽ phá hủy src gần phía trước của nó khi sao chép byte đầu tiên.
Bạn có thể nhận thấy rằng việc memmovetriển khai ở trên thậm chí không kiểm tra xem chúng có thực sự chồng chéo hay không, nó chỉ kiểm tra vị trí tương đối của chúng, nhưng chỉ điều đó sẽ làm cho bản sao an toàn. Như memcpythường sử dụng cách nhanh nhất có thể để sao chép bộ nhớ trên bất kỳ hệ thống nào, memmovethường được thực hiện như:
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);
    }
}
Đôi khi, nếu memcpyluôn sao chép "từ trước ra sau" hoặc "sau ra trước", memmovecũng có thể sử dụng memcpytrong một trong các trường hợp chồng chéo nhưng memcpythậm chí có thể sao chép theo cách khác tùy thuộc vào cách dữ liệu được căn chỉnh và / hoặc lượng dữ liệu được đã sao chép, vì vậy ngay cả khi bạn đã thử nghiệm như thế nàomemcpy sao chép trên hệ thống của mình, bạn không thể dựa vào kết quả kiểm tra đó để luôn chính xác.
Điều đó có ý nghĩa gì đối với bạn khi quyết định gọi cái nào?
Trừ khi bạn biết chắc điều đó srcvà dstkhông trùng lặp, hãy gọi memmovevì nó sẽ luôn dẫn đến kết quả chính xác và thường là nhanh nhất có thể cho trường hợp sao chép bạn yêu cầu.
Nếu bạn biết chắc chắn điều đó srcvà dstkhông trùng lặp, hãy gọi memcpyvì nó không quan trọng bạn gọi kết quả nào, cả hai sẽ hoạt động chính xác trong trường hợp đó, nhưng memmovesẽ không bao giờ nhanh hơn memcpyvà nếu bạn không may mắn, nó thậm chí có thể chậm hơn, vì vậy bạn chỉ có thể thắng cuộc gọi memcpy.
chỉ đơn giản từ tiêu chuẩn ISO / IEC: 9899, nó được mô tả tốt.
7.21.2.1 Chức năng ghi nhớ
[...]
2 Hàm memcpy sao chép n ký tự từ đối tượng được trỏ tới bởi s2 vào đối tượng được trỏ tới bởi s1. Nếu việc sao chép diễn ra giữa các đối tượng chồng lên nhau, hành vi đó là không xác định.
Và
7.21.2.2 Chức năng ghi nhớ
[...]
2 Hàm memmove sao chép n ký tự từ đối tượng được trỏ tới bởi s2 vào đối tượng được trỏ tới bởi s1. Việc sao chép diễn ra như thể n ký tự từ đối tượng được trỏ đến bởi s2 lần đầu tiên được sao chép vào một mảng tạm thời gồm n ký tự không chồng lên các đối tượng được trỏ đến bởi s1 và s2, sau đó n ký tự từ mảng tạm thời được sao chép vào vật được chỉ bởi s1.
Tôi thường sử dụng cái nào cho câu hỏi, tùy thuộc vào chức năng mà tôi cần.
Trong văn bản thuần túy memcpy()không cho phép s1và s2chồng chéo, trong khi memmove()đó.
Có hai cách rõ ràng để triển khai mempcpy(void *dest, const void *src, size_t n)(bỏ qua giá trị trả về):
for (char *p=src, *q=dest;  n-->0;  ++p, ++q)
    *q=*p;char *p=src, *q=dest;
while (n-->0)
    q[n]=p[n];Trong lần triển khai đầu tiên, việc sao chép tiến hành từ địa chỉ thấp đến cao và trong lần thứ hai, từ cao xuống thấp. Nếu phạm vi được sao chép trùng lặp (ví dụ như trường hợp khi cuộn bộ đệm khung), thì chỉ một hướng hoạt động là đúng và hướng khác sẽ ghi đè lên các vị trí mà sau đó sẽ được đọc từ đó.
Một memmove()triển khai, đơn giản nhất, sẽ kiểm tra dest<src(theo một số cách phụ thuộc vào nền tảng) và thực hiện hướng thích hợp của memcpy().
Tất nhiên, mã người dùng không thể làm điều đó, bởi vì ngay cả sau khi truyền srcvà dstđến một số loại con trỏ cụ thể, chúng (nói chung) không trỏ vào cùng một đối tượng và do đó không thể so sánh được. Nhưng thư viện tiêu chuẩn có thể có đủ kiến thức nền tảng để thực hiện so sánh như vậy mà không gây ra Hành vi không xác định.
Lưu ý rằng trong cuộc sống thực, việc triển khai có xu hướng phức tạp hơn đáng kể, để đạt được hiệu suất tối đa từ việc truyền tải lớn hơn (khi sự liên kết cho phép) và / hoặc sử dụng tốt bộ đệm dữ liệu. Đoạn mã trên chỉ là để làm cho điểm đơn giản nhất có thể.
memmove có thể xử lý các vùng nguồn và đích chồng chéo, trong khi memcpy không thể. Trong số hai, memcpy hiệu quả hơn nhiều. Vì vậy, tốt hơn hãy SỬ DỤNG memcpy nó nếu bạn có thể.
Tham khảo: https://www.youtube.com/watch?v=Yr1YnOVG-4g Tiến sĩ Jerry Cain, (Bài giảng Hệ thống nội bộ của Stanford - 7) Thời gian: 36:00
memcpy()và không phải memcopy().