memcpy () vs memmove ()


157

Estou tentando entender a diferença entre memcpy()e memmove(), e li o texto que memcpy()não cuida da origem e do destino sobrepostos memmove().

No entanto, quando executo essas duas funções em blocos de memória sobrepostos, eles fornecem o mesmo resultado. Por exemplo, considere o seguinte exemplo do MSDN na memmove()página de ajuda: -

Existe um exemplo melhor para entender as desvantagens memcpye como memmoveresolver isso?

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

Resultado:

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

1
O Microsoft CRT possui um memcpy () seguro por um bom tempo.
Hans Passant

32
Não acho que "seguro" seja a palavra certa para isso. Um cofre memcpyseria assertque as regiões não se sobreponham ao invés de intencionalmente encobrindo erros em seu código.
R .. GitHub Pare de ajudar o gelo

6
Depende se você quer dizer "seguro para o desenvolvedor" ou "seguro para o usuário final". Eu diria que fazer o que foi dito, mesmo que não seja compatível com os padrões, é a opção mais segura para o usuário final.
Kusma

desde glibc 2.19 - não funciona The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
askovpen

Você também pode ver aqui .
Ren

Respostas:


124

Não estou totalmente surpreso que seu exemplo não mostre comportamento estranho. Tente copiar str1a str1+2vez e ver o que acontece em seguida. (Na verdade, pode não fazer diferença, depende do compilador / bibliotecas.)

Em geral, o memcpy é implementado de maneira simples (mas rápida). Simplisticamente, ele apenas faz um loop sobre os dados (em ordem), copiando de um local para outro. Isso pode resultar na substituição da fonte enquanto está sendo lida.

O Memmove trabalha mais para garantir que ele lide com a sobreposição corretamente.

EDITAR:

(Infelizmente, não consigo encontrar exemplos decentes, mas eles servirão). Compare as implementações de memcpy e memmove mostradas aqui. O memcpy apenas faz um loop, enquanto o memmove executa um teste para determinar em qual direção executar o loop para evitar a corrupção dos dados. Essas implementações são bastante simples. A maioria das implementações de alto desempenho é mais complicada (envolve copiar blocos de tamanho de palavra por vez, em vez de bytes).


2
+1 Além disso, na implementação a seguir, memmovechama memcpyuma ramificação após testar os ponteiros: student.cs.uwaterloo.ca/~cs350/common/os161-src-html/…
Pascal Cuoq

Isso parece ótimo. Parece que o Visual Studio implementa um memcpy "seguro" (junto com o gcc 4.1.1, eu testei no RHEL 5 também). Escrever as versões dessas funções no clc-wiki.net fornece uma imagem clara. Obrigado.
user534785

3
O memcpy não cuida da questão sobreposta, mas o memmove cuida. Então, por que não eliminar o memcpy da biblioteca?
Alcott

37
@ Alcott: Porque memcpypode ser mais rápido.
Billy ONeal

Link fixo / webarchive de Pascal Cuoq acima: web.archive.org/web/20130722203254/http://…
JWCS

94

A memória memcpy não pode se sobrepor ou você corre o risco de comportamento indefinido, enquanto a memória memmovepode se sobrepor.

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. 

Algumas implementações do memcpy ainda podem funcionar para sobrepor entradas, mas você não pode contar com esse comportamento. Enquanto o memmove deve permitir sobreposição.


3
realmente me ajudou thaks! +1 para obter suas informações
Muthu Ganapathy Nathan

33

Só porque memcpynão precisa lidar com regiões sobrepostas, não significa que não lide com elas corretamente. A chamada com regiões sobrepostas produz um comportamento indefinido. O comportamento indefinido pode funcionar inteiramente como você espera em uma plataforma; isso não significa que seja correto ou válido.


10
Em particular, dependendo da plataforma, é possível que memcpyseja implementado exatamente da mesma maneira que memmove. Ou seja, quem escreveu o compilador não se incomodou em escrever uma memcpyfunção única .
Cam

19

Memcpy e memove fazem coisas semelhantes.

Mas, para ver uma diferença:

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

}

dá:

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

IMHO, este programa de exemplo tem algumas falhas, uma vez que o buffer str1 é acessado fora dos limites (10 bytes para copiar, o buffer tem 7 bytes de tamanho). O erro fora dos limites resulta em um comportamento indefinido. As diferenças nos resultados mostrados das chamadas memcpy () / memmove () são específicas da implementação. E o exemplo de saída não corresponde exatamente ao programa acima ... Além disso, strcpy_s () não faz parte do C AFAIK padrão (específico do MS, consulte também: stackoverflow.com/questions/36723946/… ) - Corrija-me se eu 'Estou errado.
rel

7

Sua demo não expôs os inconvenientes do memcpy por causa do compilador "ruim", ele faz um favor na versão Debug. Uma versão de lançamento, no entanto, fornece a mesma saída, mas por causa da otimização.

    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  

O registro %eaxaqui é reproduzido como um armazenamento temporário, que corrige "elegantemente" o problema de sobreposição.

A desvantagem surge ao copiar 6 bytes, bem, pelo menos parte dele.

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

Resultado:

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

Parece estranho, também é causado pela otimização.

    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  

É por isso que sempre escolho memmoveao tentar copiar 2 blocos de memória sobrepostos.


3

A diferença entre memcpye memmoveé que

  1. em memmove, a memória de origem do tamanho especificado é copiada no buffer e depois movida para o destino. Portanto, se a memória estiver sobreposta, não haverá efeitos colaterais.

  2. no caso de memcpy(), não há buffer extra usado para a memória de origem. A cópia é feita diretamente na memória para que, quando houver sobreposição de memória, obtenhamos resultados inesperados.

Isso pode ser observado pelo seguinte código:

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

A saída é:

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

6
-1 - não há nenhuma exigência para memmove realmente copiar os dados para um tampão separado
jjwchoy

este exemplo não ajuda a entender o conceito .... como a maioria dos compiladores fornecerá o mesmo resultado da saída de movimentação do mem
Jasdeep Singh Arora

1
@jjwchoy Conceitualmente, sim. O buffer geralmente seria otimizado
MM

O mesmo resultado, no Linux.
CodyChan

2

Como já apontado em outras respostas, memmoveé mais sofisticado do que memcpytal, responsável por sobreposições de memória. O resultado do memmove é definido como se o srcarquivo fosse copiado para um buffer e, em seguida, copiado para o buffer dst. Isso NÃO significa que a implementação real usa qualquer buffer, mas provavelmente faz alguma aritmética de ponteiro.


1

O compilador pode otimizar o memcpy, por exemplo:

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

Este memcpy pode ser otimizado como: x = *(int*)some_pointer;


3
Essa otimização é permitida apenas em arquiteturas que permitem intacessos desalinhados . Em algumas arquiteturas (por exemplo, Cortex-M0), a tentativa de buscar um de 32 bits intem um endereço que não seja múltiplo de quatro causará uma falha (mas memcpyfuncionaria). Se alguém estiver usando uma CPU que permita acesso não alinhado ou usando um compilador com uma palavra-chave que instrua o compilador a montar números inteiros a partir de bytes buscados separadamente, quando necessário, poderá fazer algo como #define UNALIGNED __unalignede, em seguida, `x = * (int UNALIGNED * ) some_pointer;
Supercat

2
Alguns processadores não permitem falha de acesso int desalinhada, char x = "12345"; int *i; i = *(int *)(x + 1);mas alguns o fazem porque corrigem a cópia durante a falha. Eu trabalhei em um sistema como esse e demorou um pouco para entender por que o desempenho era tão ruim.
user3431262

*(int *)some_pointeré uma estrita aliasing violação, mas você provavelmente significa que o compilador conjunto de saída que copia um int
MM

1

O código fornecido nos links http://clc-wiki.net/wiki/memcpy para memcpy parece me confundir um pouco, pois não fornece a mesma saída quando eu o implementei usando o exemplo abaixo.

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

Resultado :

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

Mas agora você pode entender por que o memmove cuidará de questões sobrepostas.


1

Esboço padrão C11

O rascunho padrão do C11 N1570 diz:

7.24.2.1 "A função memcpy":

2 A função memcpy copia n caracteres do objeto apontado por s2 para o objeto apontado por s1. Se a cópia ocorrer entre objetos que se sobrepõem, o comportamento é indefinido.

7.24.2.2 "A função memmove":

2 A função memmove copia n caracteres do objeto apontado por s2 para o objeto apontado por s1. A cópia ocorre como se os n caracteres do objeto apontado por s2 fossem primeiro copiados para uma matriz temporária de n caracteres que não se sobrepusesse aos objetos apontados por s1 e s2 e, em seguida, os n caracteres da matriz temporária sejam copiados para o objeto apontado por s1

Portanto, qualquer sobreposição memcpyleva a um comportamento indefinido, e tudo pode acontecer: ruim, nada ou até bom. Bom é raro :-)

memmove no entanto, diz claramente que tudo acontece como se um buffer intermediário fosse usado, portanto as sobreposições claramente são aceitáveis.

std::copyNo entanto, o C ++ é mais tolerante e permite sobreposições: std :: copy lida com intervalos sobrepostos?


memmoveuse uma matriz temporária extra de n, então usa memória extra? Mas como pode se não tivermos acesso a nenhuma memória? (Está usando 2x a memória).
Clmno 27/03/19

@clmno ele aloca na pilha ou malloc como qualquer outra função que eu esperaria :-)
Ciro Santilli 郝海东 法轮功 病 六四 事件

1
Eu fiz uma pergunta aqui , recebi uma boa resposta também. Obrigado. Vi o seu HackerNews pós que se tornou viral (o x86) :)
clmno

-4

Eu tentei executar o mesmo programa usando o eclipse e mostra uma clara diferença entre memcpye memmove. memcpy()não se preocupa com a sobreposição do local da memória, o que resulta em corrupção de dados, enquanto memmove()copia os dados primeiro para a variável temporária e depois copia para o local real da memória.

Ao tentar copiar dados do local str1para str1+2, a saída de memcpyé " aaaaaa". A questão seria como? memcpy()copiará um byte de cada vez da esquerda para a direita. Como mostrado no seu programa " aabbcc", todas as cópias ocorrerão como abaixo,

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

memmove() primeiro copia os dados para a variável temporária e depois copia para o local real da memória.

  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)

Saída é

memcpy : aaaaaa

memmove : aaaabb


2
Bem-vindo ao Stack Overflow. Por favor, leia a página Sobre em breve. Existem vários problemas a serem resolvidos. Em primeiro lugar, você adicionou uma resposta a uma pergunta com várias respostas de 18 meses ou mais atrás. Para garantir a adição, você precisaria fornecer novas informações surpreendentes. Segundo, você especifica o Eclipse, mas o Eclipse é um IDE que usa um compilador C, mas não identifica a plataforma em que seu código está sendo executado ou o compilador C que o Eclipse está usando. Eu ficaria interessado em saber como você verifica essas memmove()cópias para um local intermediário. Deve apenas copiar ao contrário, quando necessário.
Jonathan Leffler

Obrigado. Sobre o compilador, então eu estou usando o compilador gcc no linux. Há uma página de manual no linux para o memove que especifica claramente que o memove copiará dados em variável temporária para evitar sobreposição de dados. Aqui está o link dessa página de manual linux.die.net/man/3/memmove
Pratik Panchal

3
Na verdade, diz "como se", o que não significa que é o que realmente acontece. É verdade que poderia fazê-lo dessa maneira (embora houvesse dúvidas sobre de onde ela extrai a memória sobressalente), mas eu ficaria mais do que um pouco surpreso se fosse isso o que realmente faz. Se o endereço de origem for maior que o endereço de destino, será suficiente copiar do início ao fim (cópia para frente); se o endereço de origem for menor que o endereço de destino, será suficiente copiar do final para o início (cópia reversa). Nenhuma memória auxiliar é necessária ou usada.
Jonathan Leffler

tente explicar sua resposta com dados reais no código, isso seria mais útil.
Haseeb Mir
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.