Qual é a diferença entre memmove e memcpy?

120

Qual é a diferença entre memmovee memcpy? Qual você costuma usar e como?

Comunidade
fonte
Observe os problemas que possam surgir: lwn.net/Articles/414467
Zan Lynx

Respostas:

162

Com memcpy, o destino não pode sobrepor a fonte. Com memmoveele pode. Isso significa que memmovepode ser um pouco mais lento do que memcpy, pois não pode fazer as mesmas suposições.

Por exemplo, memcpysempre pode copiar endereços de baixo para alto. Se o destino se sobrepuser após a origem, isso significa que alguns endereços serão substituídos antes da cópia. memmovedetectaria isso e copiaria na outra direção - de alto para baixo - nesse caso. No entanto, verificar isso e mudar para outro algoritmo (possivelmente menos eficiente) leva tempo.

bdonlan
fonte
1
ao usar o memcpy, como posso garantir que os endereços src e dest não se sobreponham? Pessoalmente, devo garantir que src e dest não se sobreponham?
Alcott
6
@ Alcott, não use o memcpy se você não souber que eles não se sobrepõem - use o memmove. Quando não há sobreposição, memmove e memcpy são equivalentes (embora o memcpy possa ser muito, muito, muito ligeiramente mais rápido).
bdonlan
Você pode usar a palavra-chave 'restringir' se estiver trabalhando com matrizes longas e quiser proteger seu processo de cópia. Por exemplo, se o método utiliza como parâmetros matrizes de entrada e saída e você deve verificar se o usuário não passa o mesmo endereço que a entrada e a saída. Leia mais aqui stackoverflow.com/questions/776283/…
DanielHsH 1/01/15
10
@DanielHsH 'restringir' é uma promessa que você faz ao compilador; não é imposto pelo compilador. Se você colocar 'restringir' nos seus argumentos e, de fato, tiver sobreposição (ou, geralmente, acessar os dados restritos a partir de ponteiros derivados de vários locais), o comportamento do programa é indefinido, erros estranhos ocorrerão e o compilador normalmente não irá avisá-lo sobre isso.
bdonlan
@dondonlan Não é apenas uma promessa para o compilador, é um requisito para o chamador. É um requisito que não é imposto, mas se você violar um requisito, não poderá reclamar se obtiver resultados inesperados. Violar um requisito é um comportamento indefinido, assim como i = i++ + 1é indefinido; o compilador não proíbe que você escreva exatamente esse código, mas o resultado dessa instrução pode ser qualquer coisa e diferentes compiladores ou CPUs mostrarão valores diferentes aqui.
Mecki
33

memmovepode lidar com sobreposição de memória, memcpynão pode.

Considerar

char[] str = "foo-bar";
memcpy(&str[3],&str[4],4); //might blow up

Obviamente, a origem e o destino agora se sobrepõem, estamos substituindo "-bar" por "bar". É um comportamento indefinido usando memcpyse a origem e o destino se sobrepõem; nesse caso, precisamos memmove.

memmove(&str[3],&str[4],4); //fine
n
fonte
5
por que explodir primeiro?
4
@ultraman: Porque PODE ter sido implementado usando assembley de baixo nível que requer que a memória não se sobreponha. Nesse caso, você poderia, por exemplo, gerar uma exceção de sinal ou hardware para o processador que interrompe o aplicativo. A documentação especifica que ela não lida com a condição, mas o padrão não especifica o que acontecerá quando essas condições forem submetidas a nova verificação (isso é conhecido como comportamento indefinido). Comportamento indefinido pode fazer qualquer coisa.
297 Martin Martin York
com o gcc 4.8.2, Até o memcpy também aceita ponteiros de origem e destino sobrepostos e funciona bem.
GeekyJ
4
@jagsgediya Claro que pode. Mas como o memcpy está documentado para não suportar isso, você não deve confiar nesse comportamento específico da implementação, é por isso que o memmove () existe. Pode ser diferente em outra versão do gcc. Pode ser diferente se o gcc inline o memcpy em vez de chamar memcpy () na glibc, pode ser diferente em uma versão mais antiga ou mais recente do glibc e assim por diante.
Nos
Da prática, parece que o memcpy e o memmove fizeram a mesma coisa. Um comportamento indefinido tão profundo.
Vida Vida
22

Na página de manual do memcpy .

A função memcpy () copia n bytes da área de memória src para a área de memória dest. As áreas de memória não devem se sobrepor. Use memmove (3) se as áreas de memória se sobreporem.

John Carter
fonte
12

A principal diferença entre memmove()e memcpy()é que em memmove()um buffer - memória temporária - é usada, portanto, não há risco de sobreposição. Por outro lado, memcpy()copia diretamente os dados do local apontado pela fonte para o local apontado pelo destino . ( http://www.cplusplus.com/reference/cstring/memcpy/ )

Considere os seguintes exemplos:

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

    Como você esperava, isso será impresso:

    stackoverflow
    stackstacklow
    stackstacklow
  2. Mas neste exemplo, os resultados não serão os mesmos:

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

    Resultado:

    stackoverflow
    stackstackovw
    stackstackstw

Isso ocorre porque "memcpy ()" faz o seguinte:

1.  stackoverflow
2.  stacksverflow
3.  stacksterflow
4.  stackstarflow
5.  stackstacflow
6.  stackstacklow
7.  stackstacksow
8.  stackstackstw
Mirac Suzgun
fonte
2
Mas, parece que a saída que você mencionou está invertida !!
kumar
1
Quando eu executar o mesmo programa, eu recebo o seguinte resultado: stackoverflow meios stackstackstw stackstackstw // Não há nenhuma diferença na saída entre memcpy e memmove
kumar
4
"é que em" memmove () ", um buffer - uma memória temporária - é usado;" Não é verdade. diz "como se", então apenas tem que se comportar assim, não que tenha que ser assim. Isso é realmente relevante, pois a maioria das implementações do memmove apenas faz uma troca de XOR.
dhein
2
Eu não acho que a implementação de memmove()é necessária para usar um buffer. É perfeitamente permitido mover-se no local (desde que cada leitura seja concluída antes de qualquer gravação no mesmo endereço).
Toby Speight
12

Supondo que você teria que implementar os dois, a implementação poderia ser assim:

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

E isso deve muito bem explicar a diferença. memmovesempre copia de tal maneira, que ainda é seguro se srce se dstsobrepõe, embora memcpyapenas não importe como a documentação diz ao usar memcpy, as duas áreas de memória não devem se sobrepor.

Por exemplo, se as memcpycópias "da frente para trás" e os blocos de memória estiverem alinhados como este

[---- src ----]
            [---- dst ---]

copiando o primeiro byte srcpara dstjá destrói o conteúdo dos últimos bytes de srcantes que estes tenham sido copiadas. Apenas copiar "de trás para frente" levará a resultados corretos.

Agora troque srce dst:

[---- dst ----]
            [---- src ---]

Nesse caso, só é seguro copiar "frente para trás", pois a cópia "de trás para frente" já destruiria srcperto de sua frente ao copiar o primeiro byte.

Você deve ter notado que a memmoveimplementação acima nem testa se eles realmente se sobrepõem, apenas verifica suas posições relativas, mas somente isso tornará a cópia segura. Como memcpygeralmente usa a maneira mais rápida possível de copiar memória em qualquer sistema, memmovegeralmente é implementado como:

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

Às vezes, se memcpysempre copia "frente para trás" ou "volta para frente", memmovetambém pode ser usado memcpyem um dos casos sobrepostos, mas memcpypode ser copiado de maneira diferente, dependendo de como os dados estão alinhados e / ou quantos dados devem ser copiado, portanto, mesmo se você testou como as memcpycópias em seu sistema, não é possível confiar nesse resultado para estar sempre correto.

O que isso significa para você ao decidir para quem ligar?

  1. A menos que você tenha certeza disso srce dstnão se sobreponha, ligue memmove, pois sempre levará a resultados corretos e geralmente é o mais rápido possível para o caso de cópia necessário.

  2. Se você tem certeza disso srce dstnão se sobrepõe, ligue memcpy, pois não importa para quem você deseja o resultado, ambos funcionarão corretamente nesse caso, mas memmovenunca serão mais rápidos do que memcpye se você tiver azar, pode até seja mais lento, para que você possa ganhar apenas chamadas memcpy.

Mecki
fonte
+1 porque seus "desenhos ascii" foram úteis para entender por que não pode haver sobreposição sem
danificar
10

Um deles lida com destinos sobrepostos e o outro não.

KPexEA
fonte
6

simplesmente a partir da norma ISO / IEC: 9899, ​​está bem descrita.

7.21.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.

E

7.21.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.

Qual deles eu costumo usar de acordo com a pergunta depende de qual funcionalidade eu preciso.

Em texto sem formatação memcpy(), não permite s1e s2se sobrepõe, enquanto memmove()faz.

dhein
fonte
0

Existem duas maneiras óbvias de implementar mempcpy(void *dest, const void *src, size_t n)(ignorando o valor de retorno):

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

Na primeira implementação, a cópia prossegue dos endereços baixo para o alto e, na segunda, do alto para o baixo. Se o intervalo a ser copiado se sobrepor (como é o caso ao rolar um buffer de quadros, por exemplo), apenas uma direção da operação está correta e a outra substituirá os locais que serão lidos posteriormente.

Uma memmove()implementação, na sua forma mais simples, testará dest<src(de alguma maneira dependente da plataforma) e executará a direção apropriada de memcpy().

O código do usuário não pode fazer isso, é claro, porque mesmo depois da conversão srce dstde algum tipo de ponteiro concreto, eles não apontam (em geral) para o mesmo objeto e, portanto, não podem ser comparados. Mas a biblioteca padrão pode ter conhecimento de plataforma suficiente para realizar essa comparação sem causar comportamento indefinido.


Observe que, na vida real, as implementações tendem a ser significativamente mais complexas, para obter o desempenho máximo de transferências maiores (quando o alinhamento permitir) e / ou boa utilização do cache de dados. O código acima é apenas para esclarecer o ponto da maneira mais simples possível.

Toby Speight
fonte
0

O memmove pode lidar com regiões de origem e destino sobrepostas, enquanto o memcpy não pode. Entre os dois, o memcpy é muito mais eficiente. Então, é melhor USAR o memcpy, se puder.

Referência: https://www.youtube.com/watch?v=Yr1YnOVG-4g Dr. Jerry Cain, (Palestra sobre sistemas de introdução de Stanford - 7) Horário: 36:00

Ehsan
fonte
Essa resposta diz "possivelmente um pouco mais rápido" e fornece dados quantitativos indicando apenas uma pequena diferença. Esta resposta afirma que um é "muito mais eficiente". Quanto mais eficiente você achou o mais rápido? BTW: Suponho que você queira dizer memcpy()e não memcopy().
chux - Restabelece Monica
O comentário é feito com base na palestra do Dr. Jerry Cain. Eu pediria que você ouvisse sua palestra às 36:00, apenas 2-3 minutos serão suficientes. E obrigado pela captura. : D
Ehsan