memcpy () vs memmove ()


157

나는 차이를 이해하려고 memcpy()하고 memmove(), 나는 텍스트를 읽을 수있는 memcpy()반면, 중복 소스의 관심과 대상을 고려하지 않습니다 memmove()않습니다를.

그러나 겹치는 메모리 블록 에서이 두 기능을 실행하면 둘 다 동일한 결과를 얻습니다. 예를 들어 memmove()도움말 페이지 에서 다음 MSDN 예제를 참조하십시오.

의 단점을 이해하는 좋은 예 거기에 memcpy어떻게 memmove해결할 수있는 문제는?

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

산출:

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

1
Microsoft CRT는 꽤 오랫동안 안전한 memcpy ()를 가지고 있습니다.
Hans Passant

32
나는 "안전한"것이 올바른 단어라고 생각하지 않습니다. 안전 memcpy하면 assert코드의 버그를 의도적으로 덮지 않고 영역이 겹치지 않게됩니다.
R .. GitHub STOP HELPING ICE

6
"개발자에게 안전"또는 "최종 사용자에게 안전"을 의미하는지에 따라 다릅니다. 표준에 맞지 않더라도 최종 사용자에게는 더 안전한 선택이라고 말한대로 설명합니다.
kusma

glibc 2.19 이후-작동하지 않음 The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
askovpen

당신은 또한 여기에서 볼 수 있습니다 .
Ren

답변:


124

나는 당신의 모범이 이상한 행동을 보이지 않는다는 것에 완전히 놀랐습니다. 복사 시도 str1str1+2대신하고 어떻게되는지. (실제로 차이를 만들 수는 없으며 컴파일러 / 라이브러리에 따라 다릅니다.)

일반적으로 memcpy는 단순하지만 빠른 방식으로 구현됩니다. 간단히 말해서 데이터를 한 위치에서 다른 위치로 복사하여 순서대로 반복합니다. 이로 인해 소스를 읽는 동안 소스를 덮어 쓸 수 있습니다.

Memmove는 겹침을 올바르게 처리하기 위해 더 많은 작업을 수행합니다.

편집하다:

(불행히도 괜찮은 예를 찾을 수는 없지만 그렇게 할 것입니다). 여기에 표시된 memcpymemmove 구현을 대조 하십시오. memcpy는 루프 만하는 반면 memmove는 데이터 손상을 피하기 위해 루프 할 방향을 결정하는 테스트를 수행합니다. 이러한 구현은 다소 간단합니다. 대부분의 고성능 구현은 바이트가 아닌 한 번에 단어 크기 블록을 복사하는 것과 관련하여 더 복잡합니다.


2
+1 또한, 다음 구현에서는 포인터를 테스트 한 후 한 지점에서 memmove호출합니다 memcpy. student.cs.uwaterloo.ca/~cs350/common/os161-src-html/…
Pascal Cuoq

그거 좋을 거 같아. Visual Studio가 "안전한"memcpy를 구현하는 것처럼 보입니다 (gcc 4.1.1과 함께 RHEL 5에서도 테스트했습니다). clc-wiki.net에서 이러한 기능의 버전을 작성하면 명확한 그림을 얻을 수 있습니다. 감사.
user534785

3
memcpy는 중복 문제를 처리하지 않지만 memmove는 처리합니다. 그렇다면 왜 lib에서 memcpy를 제거하지 않습니까?
Alcott

37
@Alcott : memcpy더 빠를 수 있기 때문입니다.
Billy ONeal

: 위의 파스칼 Cuoq에서 고정 / webarchive 링크 web.archive.org/web/20130722203254/http://...
JWCS

94

메모리가 겹칠 memcpy 수 없거나 정의되지 않은 동작의 위험이있는 반면 메모리 memmove가 겹칠 수 있습니다.

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. 

memcpy의 일부 구현은 여전히 ​​겹치는 입력에 대해 작동 할 수 있지만 그 동작을 셀 수는 없습니다. memmove가 겹치는 것을 허용해야합니다.


3
정말 나를 도와주었습니다! 귀하의 정보 +1
Muthu Ganapathy Nathan 2016

33

그냥 있기 때문에 memcpy중복 영역을 처리하지 않고, 올바르게 그들과 거래를하지 않는 것을 의미하지 않는다. 영역이 겹치는 호출은 정의되지 않은 동작을 생성합니다. 정의되지 않은 동작은 하나의 플랫폼에서 예상 한대로 작동 할 수 있습니다. 그것이 정확하거나 유효하다는 것을 의미하지는 않습니다.


10
특히 플랫폼에 따라 memcpy와 동일한 방식으로 구현 될 수 memmove있습니다. 즉, 컴파일러를 작성한 사람은 고유 한 memcpy함수를 작성하지 않아도 됩니다.
Cam

19

memcpy와 memove는 비슷한 작업을 수행합니다.

그러나 한 가지 차이점을 알 수 있습니다.

#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 );

}

제공합니다 :

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

IMHO,이 예제 프로그램에는 str1 버퍼가 범위를 벗어나서 액세스되기 때문에 약간의 결함이 있습니다 (복사하려면 10 바이트, 버퍼 크기는 7 바이트). 범위를 벗어난 오류로 인해 정의되지 않은 동작이 발생합니다. memcpy () / memmove () 호출의 표시된 결과 차이는 구현에 따라 다릅니다. 그리고 예제 출력이 위의 프로그램과 정확히 일치하지 않습니다 ... 또한 strcpy_s ()는 표준 C AFAIK의 일부가 아닙니다 (MS 관련, 또한보십시오 : stackoverflow.com/questions/36723946/…)- 내가 수정하면 틀렸어
확인해

7

데모는 "나쁜"컴파일러로 인해 memcpy 단점을 노출시키지 않았으므로 디버그 버전에서 유리합니다. 그러나 릴리스 버전은 동일한 출력을 제공하지만 최적화 때문입니다.

    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  

여기서 레지스터 %eax는 임시 저장소로 재생되며 겹치는 문제를 "우아하게"수정합니다.

6 바이트를 복사 할 때 적어도 일부는 복사 할 때 단점이 발생합니다.

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

산출:

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

이상하게 보입니다. 최적화 때문이기도합니다.

    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  

이것이 memmove2 개의 겹친 메모리 블록을 복사 할 때 항상 선택하는 이유 입니다.


3

차이 memcpy하고 memmove있다는 것이다

  1. 에서 memmove지정된 크기의 소스 메모리가 버퍼에 복사 된 다음 대상으로 이동됩니다. 따라서 메모리가 겹치는 경우 부작용이 없습니다.

  2. 의 경우 memcpy()소스 메모리에 사용되는 추가 버퍼가 없습니다. 복사는 메모리에서 직접 수행되므로 메모리가 겹치면 예기치 않은 결과가 발생합니다.

다음 코드에서이를 확인할 수 있습니다.

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

출력은 다음과 같습니다

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

6
-1-memmove가 실제로 별도의 버퍼에 데이터를 복사 할 필요는 없습니다.
jjwchoy

이 예제는 개념을 이해하는 데 도움이되지 않습니다. .... 대부분의 컴파일러는 mem move 출력과 동일하게 할 것입니다
Jasdeep Singh Arora

1
@jjwchoy 개념적으로 그렇습니다. 버퍼는 일반적으로 최적화됩니다
MM

Linux에서도 동일한 결과가 나타납니다.
CodyChan

2

다른 답변에서 이미 지적했듯이 메모리 중복을 설명하는 것 memmove보다 더 정교 memcpy합니다. memmove의 결과는 마치 src버퍼에 복사 된 다음 버퍼에 복사 된 것처럼 정의됩니다 dst. 이것은 실제 구현이 버퍼를 사용한다는 것을 의미하지는 않지만 아마도 포인터 산술을 수행합니다.


1

컴파일러는 다음과 같이 memcpy를 최적화 할 수 있습니다.

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

이 memcpy는 다음과 같이 최적화 될 수 있습니다. x = *(int*)some_pointer;


3
이러한 최적화는 정렬되지 않은 int액세스 를 허용하는 아키텍처에서만 허용됩니다 . 일부 아키텍처 (예 : Cortex-M0)에서는 int4의 배수가 아닌 주소에서 32 비트를 가져 오려고 하면 충돌이 발생하지만 memcpy작동합니다. 정렬되지 않은 액세스를 허용하는 CPU를 사용하거나 필요한 경우 컴파일러가 별도로 가져온 바이트에서 정수를 어셈블하도록 지시하는 키워드와 함께 컴파일러를 사용하는 경우 #define UNALIGNED __unaligned다음 과 같은 작업을 수행하고 `x = * (int UNALIGNED *) ) some_pointer;
supercat

2
일부 프로세서는 정렬되지 않은 int 액세스 충돌을 허용하지 않지만 일부 프로세서는 char x = "12345"; int *i; i = *(int *)(x + 1);결함이 발생한 동안 사본을 수정하기 때문에 수행합니다. 나는 이와 같은 시스템을 연구했으며 성능이 왜 그렇게 열악했는지 이해하는 데 약간의 시간이 걸렸습니다.
user3431262

*(int *)some_pointer엄격한 앨리어싱 위반이지만 컴파일러는 int를 복사하는 어셈블리를 출력 할 것입니다.
MM

1

memcpy에 대한 http://clc-wiki.net/wiki/memcpy 링크에 제공된 코드 는 아래 예제를 사용하여 구현했을 때 동일한 출력을 제공하지 않기 때문에 약간 혼란 스럽습니다.

#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();
}

출력 :

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

그러나 이제 memmove 가 겹치는 문제를 처리 하는 이유를 이해할 수 있습니다 .


1

C11 표준 초안

C11 N1570 표준 초안은 말합니다 :

7.24.2.1 "memcpy 함수":

2 memcpy 함수는 s2가 가리키는 오브젝트에서 s1이 가리키는 오브젝트로 n 개의 문자를 복사합니다. 겹치는 객체간에 복사가 수행되면 동작이 정의되지 않습니다.

7.24.2.2 "멤버 기능":

2 memmove 함수는 s2가 가리키는 객체에서 s1이 가리키는 객체로 n 개의 문자를 복사합니다. s2가 가리키는 객체의 n 문자가 s1 및 s2가 가리키는 객체와 겹치지 않는 n 문자의 임시 배열로 먼저 복사 된 다음 임시 배열의 n 문자가 복사되는 것처럼 복사가 수행됩니다. s1이 가리키는 객체

따라서 오버랩 memcpy이 발생하면 정의되지 않은 동작이 발생하고 나쁜, 아무것도 또는 심지어 좋은 일이 발생할 수 있습니다. 그래도 좋은 일은 드물다 :-)

memmove 그러나 중간 버퍼가 사용되는 것처럼 모든 것이 발생하므로 분명히 겹치는 것이 좋습니다.

std::copy그러나 C ++ 은 더 관대하며 겹치기를 허용합니다. std :: copy가 겹치는 범위를 처리합니까?


memmove여분의 임시 n 배열을 사용하므로 여분의 메모리를 사용합니까? 그러나 메모리에 대한 액세스 권한이 없다면 어떻게 할 수 있습니까? (메모리를 2 배 사용하고 있습니다).
clmno

@clmno 그것은 내가 기대할 수있는 다른 함수처럼 스택이나 malloc에 ​​할당한다 :-)
Ciro Santilli illi 海东 冠状 病 六四 事件 法轮功

1
나는 여기 에 질문을 했고 좋은 대답도 얻었습니다. 감사합니다. 바이러스에 감염된 hackernews 게시물 을 보았습니다 (x86) :)
clmno

-4

나는 이클립스를 사용하여 같은 프로그램을 실행하려고하고 그것을 사이에 분명한 차이를 보여줍니다 memcpymemmove. memcpy()메모리 위치의 겹침에 신경 쓰지 않아 데이터가 손상되는 반면 memmove()데이터를 임시 변수에 먼저 복사 한 다음 실제 메모리 위치에 복사합니다.

위치에서 (으) str1로 데이터를 복사 하는 str1+2중 출력 memcpy은 " aaaaaa"입니다. 문제는 어떻게 될까? memcpy()왼쪽에서 오른쪽으로 한 번에 한 바이트 씩 복사합니다. 프로그램 " aabbcc"에 표시된대로 모든 복사는 다음과 같이 진행됩니다.

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

memmove() 데이터를 임시 변수에 먼저 복사 한 다음 실제 메모리 위치에 복사합니다.

  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)

출력

memcpy : aaaaaa

memmove : aaaabb


2
스택 오버플로에 오신 것을 환영합니다. 정보 페이지를 곧 읽으십시오 . 해결해야 할 다양한 문제가 있습니다. 무엇보다도 18 개월 전부터 여러 답변이있는 질문에 대한 답변을 추가했습니다. 추가를 보증하려면 놀라운 새 정보를 제공해야합니다. 둘째, Eclipse를 지정하지만 Eclipse는 C 컴파일러를 사용하는 IDE이지만 코드가 실행되고 있거나 Eclipse가 사용중인 플랫폼을 식별하지 못합니다. memmove()사본을 중간 위치로 확인하는 방법을 알고 싶습니다 . 필요할 때 반대로 복사해야합니다.
Jonathan Leffler

감사. 컴파일러에 대해 Linux에서 gcc 컴파일러를 사용하고 있습니다. linux에는 memove에 대한 man 페이지가 있는데, 이는 memove가 데이터가 겹치지 않도록 임시 변수로 데이터를 복사하도록 명확하게 지정합니다. 다음은 해당 매뉴얼 페이지의 링크입니다. linux.die.net/man/3/memmove
Pratik Panchal

3
실제로 "as as"라고되어 있는데, 이것이 실제로 일어나는 것을 의미하지는 않습니다. 그것이 부여 할 수 (그에서 여분의 메모리를 얻을 수있는 위치에 대한 질문이있을 거라고하지만) 실제로 그런 식으로 할,하지만 난 그게 실제로 무엇이라면 조금 놀랐보다 더 될 것입니다. 소스 주소가 대상 주소보다 큰 경우 처음부터 끝까지 복사하면 충분합니다 (앞으로 복사). 소스 주소가 대상 주소보다 작 으면 끝에서 시작 (뒤로 복사)으로 복사하면됩니다. 보조 메모리가 필요하지 않습니다.
Jonathan Leffler 2016 년

코드에서 실제 데이터로 답변을 설명하십시오. 더 도움이 될 것입니다.
HaseeB Mir
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.