Sự khác biệt giữa memmove và memcpy là gì?


Câu trả lời:


162

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.


1
khi sử dụng memcpy, làm cách nào để đảm bảo rằng địa chỉ src và dest không trùng nhau? Cá nhân tôi có nên đảm bảo rằng src và dest không trùng nhau không?
Alcott

6
@Alcott, không sử dụng memcpy nếu bạn không biết rằng chúng không trùng nhau - thay vào đó hãy sử dụng memmove. Khi không có sự chồng chéo, memmove và memcpy là tương đương nhau (mặc dù memcpy có thể nhanh hơn một chút).
bdonlan

Bạn có thể sử dụng từ khóa 'limit' nếu bạn đang làm việc với các mảng dài và muốn bảo vệ quá trình sao chép của mình. Ví dụ: nếu phương thức của bạn nhận là mảng đầu vào và đầu ra tham số và bạn phải xác minh rằng người dùng không chuyển cùng địa chỉ làm đầu vào và đầu ra. Đọc thêm tại đây stackoverflow.com/questions/776283/…
DanielHsH

10
@DanielHsH 'hạn chế' là một lời hứa bạn thực hiện trình biên dịch; nó không được thực thi bởi trình biên dịch. Nếu bạn đặt 'giới hạn' đối với các đối số của mình và trên thực tế, có sự chồng chéo (hoặc nói chung, truy cập dữ liệu bị hạn chế từ con trỏ xuất phát từ nhiều nơi), hành vi của chương trình là không xác định, các lỗi lạ sẽ xảy ra và trình biên dịch thường sẽ không cảnh báo bạn về điều đó.
bdonlan

@bdonlan Đây không chỉ là một lời hứa với trình biên dịch mà còn là một yêu cầu đối với người gọi của bạn. Đó là một yêu cầu không được thực thi nhưng nếu bạn vi phạm một yêu cầu, bạn không thể khiếu nại nếu bạn nhận được kết quả không mong muốn. Vi phạm một yêu cầu là hành vi không xác định, giống như 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.
Mecki

33

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

5
tại sao lần đầu tiên nổ tung?

4
@ultraman: Bởi vì nó CÓ THỂ được triển khai bằng cách sử dụng tập hợp mức thấp yêu cầu bộ nhớ không chồng chéo. Nếu nó xảy ra, chẳng hạn, bạn có thể tạo tín hiệu hoặc ngoại lệ phần cứng cho bộ xử lý hủy ứng dụng. Tài liệu chỉ rõ rằng nó không xử lý điều kiện, nhưng tiêu chuẩn không chỉ định điều gì sẽ xảy ra khi các điều kiện này bị ảnh hưởng (đây được gọi là hành vi không xác định). Hành vi không xác định có thể làm bất cứ điều gì.
Martin York

với gcc 4.8.2, ngay cả memcpy cũng chấp nhận con trỏ nguồn và đích chồng chéo và hoạt động tốt.
GeekyJ

4
@jagsgediya Chắc chắn có thể. Nhưng vì memcpy được ghi nhận là không hỗ trợ điều này, bạn không nên dựa vào hành vi triển khai cụ thể đó, đó là lý do tại sao memmove () tồn tại. Nó có thể khác trong một phiên bản gcc khác. Có thể khác nếu gcc nội dòng memcpy thay vì gọi đến memcpy () trong glibc, thì nó có thể khác trên phiên bản glibc cũ hơn hoặc mới hơn, v.v.
nos

Từ thực tế, có vẻ như memcpy và memmove đã làm điều tương tự. Đó là một hành vi sâu sắc không xác định.
Cuộc sống

22

Từ trang người đàn ông memcpy .

Hàm memcpy () sao chép n byte từ vùng nhớ src sang vùng nhớ dest. Các vùng nhớ không được chồng lên nhau. Sử dụng memmove (3) nếu các vùng bộ nhớ trùng nhau.


12

Sự khác biệt chính giữa memmove()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:

  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;
    }

    Như bạn mong đợi, điều này sẽ in ra:

    stackoverflow
    stackstacklow
    stackstacklow
  2. Như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

2
Nhưng, có vẻ như đầu ra bạn đề cập đã bị đảo ngược !!
kumar

1
Khi tôi chạy cùng một chương trình, tôi nhận được kết quả sau: stackoverflow stackstackstw stackstackstw // nghĩa là không có sự khác biệt về sản lượng giữa memcpy và memmove
kumar

4
"là trong" memmove () ", một bộ đệm - một bộ nhớ tạm thời - được sử dụng;" Là không đúng sự thật. nó nói "như thể" vì vậy nó chỉ phải cư xử như vậy, không phải là nó phải như vậy. Điều đó thực sự có liên quan vì hầu hết các triển khai bản ghi nhớ chỉ thực hiện một hoán đổi XOR.
dhein

2
Tôi không nghĩ rằng việc triển khai 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ỉ).
Toby Speight

12

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 srcdstchồ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 srcdst:

[---- 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?

  1. Trừ khi bạn biết chắc điều đó srcdstkhô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.

  2. Nếu bạn biết chắc chắn điều đó srcdstkhô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.


1 vì "bản vẽ ascii" của bạn là hữu ích để hiểu tại sao lại có thể không được chồng chéo mà không làm hư hỏng dữ liệu
Scylardor

10

Một cái xử lý các điểm đến chồng chéo, cái kia thì không.


6

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.

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 s1s2chồng chéo, trong khi memmove()đó.


0

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ề):

  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];

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 srcdstđế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ể.


0

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


Câu trả lời này cho biết "có thể nhanh hơn một chút" và cung cấp dữ liệu định lượng chỉ cho thấy một sự khác biệt nhỏ. Câu trả lời này khẳng định một câu trả lời là "hiệu quả hơn nhiều". Bạn đã tìm thấy cái nhanh hơn hiệu quả hơn bao nhiêu? BTW: Tôi cho rằng bạn có ý nghĩa memcpy()và không phải memcopy().
chux - Phục hồi Monica

Nhận xét được đưa ra dựa trên bài giảng của Tiến sĩ Jerry Cain. Tôi yêu cầu bạn nghe bài giảng của anh ấy vào lúc 36:00, chỉ cần 2-3 phút là đủ. Và cảm ơn vì đã bắt được. : D
Ehsan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.