memcpy () vs memmove ()


157

Tôi đang cố gắng để hiểu sự khác biệt giữa memcpy()memmove(), và tôi đã đọc văn bản memcpy()không quan tâm đến nguồn và đích chồng chéo trong khi memmove()đó.

Tuy nhiên, khi tôi thực hiện hai chức năng này trên các khối bộ nhớ chồng chéo, cả hai đều cho kết quả như nhau. Ví dụ: lấy ví dụ MSDN sau trên memmove()trang trợ giúp: -

Có một ví dụ tốt hơn để hiểu những nhược điểm của memcpyvà làm thế nào để memmovegiải quyết nó?

// crt_memcpy.c
// Illustrate overlapping copy: memmove always handles it correctly; memcpy may handle
// it correctly.

#include <memory.h>
#include <string.h>
#include <stdio.h>

char str1[7] = "aabbcc";

int main( void )
{
    printf( "The string: %s\n", str1 );
    memcpy( str1 + 2, str1, 4 );
    printf( "New string: %s\n", str1 );

    strcpy_s( str1, sizeof(str1), "aabbcc" );   // reset string

    printf( "The string: %s\n", str1 );
    memmove( str1 + 2, str1, 4 );
    printf( "New string: %s\n", str1 );
}

Đầu ra:

The string: aabbcc
New string: aaaabb
The string: aabbcc
New string: aaaabb

1
Microsoft CRT đã có một memcpy () an toàn trong một thời gian khá lâu.
Hans Passant

32
Tôi không nghĩ "an toàn" là từ thích hợp cho nó. Két an toàn memcpysẽ assertlà khu vực không chồng chéo lên nhau chứ không phải là cố tình bao che cho lỗi trong mã của bạn.
R .. GitHub DỪNG GIÚP ICE

6
Phụ thuộc vào việc bạn có nghĩa là "an toàn cho nhà phát triển" hay "an toàn cho người dùng cuối". Tôi sẽ lập luận rằng làm như đã nói, ngay cả khi nó không tuân thủ tiêu chuẩn là lựa chọn an toàn hơn cho người dùng cuối.
kusma

kể từ khi glibc 2.19 - không hoạt động The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
Askovpen

Bạn cũng có thể xem ở đây .
Ren

Câu trả lời:


124

Tôi không hoàn toàn ngạc nhiên khi ví dụ của bạn thể hiện không có hành vi lạ. Hãy thử sao chép str1vàostr1+2 thay thế và xem những gì xảy ra sau đó. (Có thể không thực sự tạo ra sự khác biệt, phụ thuộc vào trình biên dịch / thư viện.)

Nói chung, memcpy được thực hiện theo cách đơn giản (nhưng nhanh chóng). Đơn giản, nó chỉ lặp trên dữ liệu (theo thứ tự), sao chép từ vị trí này sang vị trí khác. Điều này có thể dẫn đến nguồn bị ghi đè trong khi nó được đọc.

Memmove thực hiện nhiều công việc hơn để đảm bảo nó xử lý trùng lặp chính xác.

BIÊN TẬP:

(Thật không may, tôi không thể tìm thấy các ví dụ phong nha, nhưng chúng sẽ làm được). Tương phản các triển khai memcpymemmove được hiển thị ở đây. memcpy chỉ là các vòng lặp, trong khi memmove thực hiện một bài kiểm tra để xác định hướng nào sẽ lặp lại để tránh làm hỏng dữ liệu. Những triển khai này khá đơn giản. Hầu hết các triển khai hiệu suất cao đều phức tạp hơn (liên quan đến việc sao chép các khối kích thước từ tại một thời điểm thay vì byte).


2
+1 Ngoài ra, trong triển khai sau, hãy memmovegọi memcpymột nhánh sau khi kiểm tra các con trỏ: student.cs.uwaterloo.ca/~cs350/common/os161-src-html/iêu
Pascal Cuoq

Nghe có vẻ tuyệt vời. Có vẻ như Visual Studio triển khai một memcpy "an toàn" (cùng với gcc 4.1.1, tôi cũng đã thử nghiệm trên RHEL 5). Viết các phiên bản của các chức năng này từ clc-wiki.net cho một bức tranh rõ ràng. Cảm ơn.
dùng534785

3
memcpy không quan tâm đến vấn đề chồng chéo, nhưng memmove thì có. Vậy thì tại sao không loại bỏ memcpy khỏi lib?
Alcott

37
@ Alcott: Vì memcpycó thể nhanh hơn.
Billy ONeal

Cố định / link webarchive từ Pascal Cuoq trên: web.archive.org/web/20130722203254/http://...
JWCS

94

Bộ nhớ trong memcpy không thể chồng lấp hoặc bạn có nguy cơ hành vi không xác định, trong khi bộ nhớ trong memmovecó thể chồng lấp.

char a[16];
char b[16];

memcpy(a,b,16);           // valid
memmove(a,b,16);          // Also valid, but slower than memcpy.
memcpy(&a[0], &a[1],10);  // Not valid since it overlaps.
memmove(&a[0], &a[1],10); // valid. 

Một số triển khai của memcpy vẫn có thể hoạt động cho các đầu vào chồng chéo nhưng bạn không thể đếm được hành vi đó. Trong khi memmove phải cho phép chồng chéo.


3
nó thực sự giúp tôi thaks! +1 cho thông tin của bạn
Muthu Ganapathy Nathan

33

Chỉ vì memcpykhông phải đối phó với các vùng chồng lấn, không có nghĩa là nó không giải quyết chúng một cách chính xác. Cuộc gọi với các vùng chồng chéo tạo ra hành vi không xác định. Hành vi không xác định có thể hoạt động hoàn toàn như bạn mong đợi trên một nền tảng; điều đó không có nghĩa là nó đúng hoặc hợp lệ.


10
Đặc biệt, tùy thuộc vào nền tảng, có thể memcpyđược triển khai chính xác theo cách tương tự như memmove. Đó là, bất cứ ai đã viết trình biên dịch đều không bận tâm đến việc viết một memcpyhàm duy nhất .
Cam

19

Cả memcpy và memove đều làm những việc tương tự nhau.

Nhưng để nhận ra một sự khác biệt:

#include <memory.h>
#include <string.h>
#include <stdio.h>

char str1[7] = "abcdef";

int main()
{

   printf( "The string: %s\n", str1 );
   memcpy( (str1+6), str1, 10 );
   printf( "New string: %s\n", str1 );

   strcpy_s( str1, sizeof(str1), "aabbcc" );   // reset string


   printf("\nstr1: %s\n", str1);
   printf( "The string: %s\n", str1 );
   memmove( (str1+6), str1, 10 );
   printf( "New string: %s\n", str1 );

}

cho:

The string: abcdef
New string: abcdefabcdefabcd
The string: abcdef
New string: abcdefabcdef

IMHO, chương trình ví dụ này có một số sai sót, vì bộ đệm str1 được truy cập ngoài giới hạn (10 byte để sao chép, bộ đệm có kích thước 7 byte). Lỗi ngoài giới hạn dẫn đến hành vi không xác định. Sự khác biệt trong kết quả hiển thị của các lệnh gọi memcpy () / memmove () là cụ thể thực hiện. Và đầu ra ví dụ không khớp chính xác với chương trình ở trên ... Ngoài ra, strcpy_s () không phải là một phần của tiêu chuẩn C AFAIK (cụ thể của MS, xem thêm: stackoverflow.com/questions/36723946/iêu ) - Vui lòng sửa cho tôi nếu tôi 'tôi sai.
rel

7

Bản demo của bạn không phơi bày nhược điểm của memcpy vì trình biên dịch "xấu", nó giúp bạn trong phiên bản Debug. Một phiên bản phát hành, tuy nhiên, cung cấp cho bạn cùng một đầu ra, nhưng vì tối ưu hóa.

    memcpy(str1 + 2, str1, 4);
00241013  mov         eax,dword ptr [str1 (243018h)]  // load 4 bytes from source string
    printf("New string: %s\n", str1);
00241018  push        offset str1 (243018h) 
0024101D  push        offset string "New string: %s\n" (242104h) 
00241022  mov         dword ptr [str1+2 (24301Ah)],eax  // put 4 bytes to destination
00241027  call        esi  

Sổ đăng ký %eax ở đây đóng vai trò là một bộ lưu trữ tạm thời, "thanh lịch" khắc phục vấn đề chồng chéo.

Hạn chế nổi lên khi sao chép 6 byte, ít nhất là một phần của nó.

char str1[9] = "aabbccdd";

int main( void )
{
    printf("The string: %s\n", str1);
    memcpy(str1 + 2, str1, 6);
    printf("New string: %s\n", str1);

    strcpy_s(str1, sizeof(str1), "aabbccdd");   // reset string

    printf("The string: %s\n", str1);
    memmove(str1 + 2, str1, 6);
    printf("New string: %s\n", str1);
}

Đầu ra:

The string: aabbccdd
New string: aaaabbbb
The string: aabbccdd
New string: aaaabbcc

Trông có vẻ lạ, đó cũng là do tối ưu hóa.

    memcpy(str1 + 2, str1, 6);
00341013  mov         eax,dword ptr [str1 (343018h)] 
00341018  mov         dword ptr [str1+2 (34301Ah)],eax // put 4 bytes to destination, earlier than the above example
0034101D  mov         cx,word ptr [str1+4 (34301Ch)]  // HA, new register! Holding a word, which is exactly the left 2 bytes (after 4 bytes loaded to %eax)
    printf("New string: %s\n", str1);
00341024  push        offset str1 (343018h) 
00341029  push        offset string "New string: %s\n" (342104h) 
0034102E  mov         word ptr [str1+6 (34301Eh)],cx  // Again, pulling the stored word back from the new register
00341035  call        esi  

Đây là lý do tại sao tôi luôn chọn memmovekhi sao chép 2 khối bộ nhớ chồng chéo.


3

Sự khác biệt giữa memcpymemmove

  1. trong memmove, bộ nhớ nguồn có kích thước được chỉ định được sao chép vào bộ đệm và sau đó được chuyển đến đích. Vì vậy, nếu bộ nhớ bị chồng chéo, không có tác dụng phụ.

  2. trong trường hợp memcpy(), không có bộ đệm bổ sung nào được lấy cho bộ nhớ nguồn. Việc sao chép được thực hiện trực tiếp trên bộ nhớ để khi có bộ nhớ trùng lặp, chúng ta sẽ nhận được kết quả không mong muốn.

Chúng có thể được quan sát bởi đoạn mã sau:

//include string.h, stdio.h, stdlib.h
int main(){
  char a[]="hare rama hare rama";

  char b[]="hare rama hare rama";

  memmove(a+5,a,20);
  puts(a);

  memcpy(b+5,b,20);
  puts(b);
}

Đầu ra là:

hare hare rama hare rama
hare hare hare hare hare hare rama hare rama

6
-1 - không có yêu cầu cho memmove thực sự sao chép dữ liệu vào một bộ đệm riêng
jjwchoy

ví dụ này không giúp hiểu được khái niệm này .... vì hầu hết các trình biên dịch sẽ cung cấp giống như đầu ra di chuyển mem
Jasdeep Singh Arora

1
@jjwchoy Về mặt khái niệm thì có. Bộ đệm thường sẽ được tối ưu hóa
MM

Kết quả tương tự, trên Linux.
CodyChan

2

Như đã chỉ ra trong các câu trả lời khác, memmovephức tạp hơn so với memcpyviệc nó chiếm phần chồng lấp bộ nhớ. Kết quả của memmove được định nghĩa như thể srcđược sao chép vào một bộ đệm và sau đó bộ đệm được sao chép vào dst. Điều này KHÔNG có nghĩa là việc triển khai thực tế sử dụng bất kỳ bộ đệm nào, nhưng có lẽ là một số số học con trỏ.


1

trình biên dịch có thể tối ưu hóa memcpy, ví dụ:

int x;
memcpy(&x, some_pointer, sizeof(int));

Memcpy này có thể được tối ưu hóa như: x = *(int*)some_pointer;


3
Tối ưu hóa như vậy chỉ được phép trên các kiến ​​trúc cho phép inttruy cập không được phân bổ . Trên một số kiến ​​trúc (ví dụ Cortex-M0), cố gắng tìm nạp 32 bitint từ một địa chỉ không phải là bội số của bốn sẽ gây ra sự cố (nhưng memcpysẽ hoạt động). Nếu một người sẽ sử dụng CPU cho phép truy cập không được phân bổ hoặc sử dụng trình biên dịch với từ khóa chỉ đạo trình biên dịch để lắp ráp các số nguyên ra khỏi các byte được tìm nạp riêng khi cần, thì người ta có thể làm một cái gì đó như #define UNALIGNED __unalignedvà sau đó `x = * (int UNALIGNED * ) some_pulum;
supercat

2
Một số bộ xử lý không cho phép sự cố truy cập int không được chỉ định char x = "12345"; int *i; i = *(int *)(x + 1);Nhưng một số thì vì chúng sửa lỗi trong quá trình sửa lỗi. Tôi đã làm việc trên một hệ thống như thế này và phải mất một chút thời gian để hiểu tại sao hiệu suất lại kém như vậy.
dùng3431262

*(int *)some_pointer là một vi phạm bí danh nghiêm ngặt, nhưng bạn có thể có nghĩa là trình biên dịch sẽ xuất ra cụm sao chép một int
MM

1

Mã được đưa ra trong các liên kết http://clc-wiki.net/wiki/memcpy cho memcpy dường như làm tôi bối rối một chút, vì nó không cho cùng một đầu ra khi tôi triển khai nó bằng ví dụ dưới đây.

#include <memory.h>
#include <string.h>
#include <stdio.h>

char str1[11] = "abcdefghij";

void *memcpyCustom(void *dest, const void *src, size_t n)
{
    char *dp = (char *)dest;
    const char *sp = (char *)src;
    while (n--)
        *dp++ = *sp++;
    return dest;
}

void *memmoveCustom(void *dest, const void *src, size_t n)
{
    unsigned char *pd = (unsigned char *)dest;
    const unsigned char *ps = (unsigned char *)src;
    if ( ps < pd )
        for (pd += n, ps += n; n--;)
            *--pd = *--ps;
    else
        while(n--)
            *pd++ = *ps++;
    return dest;
}

int main( void )
{
    printf( "The string: %s\n", str1 );
    memcpy( str1 + 1, str1, 9 );
    printf( "Actual memcpy output: %s\n", str1 );

    strcpy_s( str1, sizeof(str1), "abcdefghij" );   // reset string

    memcpyCustom( str1 + 1, str1, 9 );
    printf( "Implemented memcpy output: %s\n", str1 );

    strcpy_s( str1, sizeof(str1), "abcdefghij" );   // reset string

    memmoveCustom( str1 + 1, str1, 9 );
    printf( "Implemented memmove output: %s\n", str1 );
    getchar();
}

Đầu ra:

The string: abcdefghij
Actual memcpy output: aabcdefghi
Implemented memcpy output: aaaaaaaaaa
Implemented memmove output: aabcdefghi

Nhưng bây giờ bạn có thể hiểu tại sao memmove sẽ quan tâm đến vấn đề chồng chéo.


1

Dự thảo tiêu chuẩn C11

Các C11 N1570 dự thảo tiêu chuẩn nói:

7.24.2.1 "Hàm memcpy":

2 Hàm memcpy sao chép n ký tự từ đối tượng được trỏ bởi s2 vào đối tượng được trỏ bởi s1. Nếu sao chép diễn ra giữa các đối tượng chồng lấp, hành vi không được xác định.

7.24.2.2 "Hàm memmove":

2 Hàm memmove sao chép n ký tự từ đối tượng được trỏ bởi s2 vào đối tượng được trỏ bởi s1. Việc sao chép diễn ra như thể các ký tự n từ đối tượng được trỏ bởi s2 trước tiên được sao chép vào một mảng tạm thời của n ký tự không trùng với các đối tượng được trỏ bởi s1 và s2, sau đó các ký tự n từ mảng tạm thời được sao chép vào đối tượng được chỉ bởi s1

Do đó, bất kỳ sự chồng chéo nào memcpydẫn đến hành vi không xác định, và bất cứ điều gì có thể xảy ra: xấu, không có gì hoặc thậm chí tốt. Tốt là rất hiếm mặc dù :-)

memmove tuy nhiên rõ ràng nói rằng mọi thứ xảy ra như thể một bộ đệm trung gian được sử dụng, vì vậy rõ ràng trùng lặp là ổn.

std::copyTuy nhiên, C ++ dễ tha thứ hơn và cho phép chồng lấp: std :: copy có xử lý các phạm vi chồng lấp không?


memmovesử dụng thêm một mảng tạm thời của n, vậy nó có sử dụng thêm bộ nhớ không? Nhưng làm sao có thể nếu chúng ta không cho nó quyền truy cập vào bất kỳ bộ nhớ nào. (Đó là sử dụng gấp 2 lần bộ nhớ).
clmno

@clmno nó phân bổ trên stack hoặc malloc như bất kỳ chức năng nào khác mà tôi mong đợi :-)
Ciro Santilli 冠状 病 六四 法轮功

1
Tôi đã hỏi một câu hỏi ở đây , cũng có một câu trả lời tốt. Cảm ơn bạn. Xem bài đăng hackernews của bạn đã lan truyền (một x86) :)
clmno

-4

Tôi đã cố gắng chạy cùng một chương trình bằng cách sử dụng nhật thực và nó cho thấy sự khác biệt rõ ràng giữa memcpymemmove. memcpy()không quan tâm đến sự chồng chéo của vị trí bộ nhớ dẫn đến hỏng dữ liệu, trong khi đó memmove()sẽ sao chép dữ liệu sang biến tạm thời trước rồi sao chép vào vị trí bộ nhớ thực.

Trong khi cố gắng sao chép dữ liệu từ vị trí str1sang str1+2, đầu ra memcpylà " aaaaaa". Câu hỏi sẽ là như thế nào? memcpy()sẽ sao chép một byte mỗi lần từ trái sang phải. Như được hiển thị trong chương trình của bạn " aabbcc", sau đó tất cả sao chép sẽ diễn ra như dưới đây,

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

memmove() sẽ sao chép dữ liệu vào biến tạm thời trước rồi sao chép vào vị trí bộ nhớ thực.

  1. aabbcc(actual) -> aabbcc(temp)

  2. aabbcc(temp) -> aaabcc(act)

  3. aabbcc(temp) -> aaaacc(act)

  4. aabbcc(temp) -> aaaabc(act)

  5. aabbcc(temp) -> aaaabb(act)

Đầu ra là

memcpy : aaaaaa

memmove : aaaabb


2
Chào mừng bạn đến với Stack Overflow. Vui lòng đọc trang Giới thiệu sớm. Có nhiều vấn đề khác nhau để giải quyết. Đầu tiên và quan trọng nhất, bạn đã thêm một câu trả lời cho một câu hỏi với nhiều câu trả lời từ 18 tháng trở lên. Để đảm bảo bổ sung, bạn sẽ cần cung cấp thông tin mới đáng ngạc nhiên. Thứ hai, bạn chỉ định Eclipse, nhưng Eclipse là một IDE sử dụng trình biên dịch C, nhưng bạn không xác định nền tảng nơi mã của bạn đang chạy hoặc trình biên dịch C mà Eclipse đang sử dụng. Tôi rất muốn biết làm thế nào bạn xác định được memmove()bản sao đó đến một vị trí trung gian. Nó chỉ nên sao chép ngược khi cần thiết.
Jonathan Leffler

Cảm ơn. Về trình biên dịch, vì vậy tôi đang sử dụng trình biên dịch gcc trên linux. Có một trang man trong linux cho memove, trong đó xác định rõ rằng memove sẽ sao chép dữ liệu trong biến tạm thời để tránh chồng chéo dữ liệu. Đây là liên kết của trang người đàn ông đó linux.die.net/man/3/memmove
Pratik Panchal

3
Nó thực sự nói "như thể", điều đó không có nghĩa đó là những gì thực sự xảy ra. Cứ cho là nó thực sự có thể làm theo cách đó (mặc dù có câu hỏi về việc nó lấy bộ nhớ dự phòng từ đâu), nhưng tôi sẽ ngạc nhiên hơn nếu đó là những gì nó thực sự làm. Nếu địa chỉ nguồn lớn hơn địa chỉ đích, thì đủ để sao chép từ đầu đến cuối (sao chép chuyển tiếp); nếu địa chỉ nguồn nhỏ hơn địa chỉ đích, thì đủ để sao chép từ đầu đến cuối (sao chép ngược). Không có bộ nhớ phụ là cần thiết hoặc được sử dụng.
Jonathan Leffler

cố gắng giải thích câu trả lời của bạn với dữ liệu thực tế trong mã, điều đó sẽ hữu ích hơn.
HaseeB Mir
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.