Como implementar o memmove no padrão C sem uma cópia intermediária?

Na página do manual no meu sistema:

void * memmove (void * dst, const void * src, size_t len);

DESCRIÇÃO
A função memmove () copia os bytes len da string src para a string dst.
As duas cordas podem se sobrepor; a cópia é sempre feita de forma não destrutiva
maneira.

Do padrão C99:

6.5.8.5 Quando dois ponteiros são comparados, o resultado depende dos locais relativos no espaço de endereço dos objetos apontados. Se dois ponteiros para o objeto ou tipos incompletos apontam para o mesmo objeto, ou ambos apontam um após o último elemento do mesmo objeto de matriz, eles se comparam iguais. Se os objetos apontados são membros do mesmo objeto agregado, ponteiros para membros da estrutura declarados posteriormente são comparados com ponteiros com membros declarados anteriormente na estrutura e ponteiros para elementos de matriz com valores de subscrito maiores são comparados com ponteiros com elementos da mesma matriz com valores mais baixos de índice. Todos os ponteiros para membros do mesmo objeto de união são iguais. Se a expressãoP aponta para um elemento de um objeto de matriz e a expressão Q aponta para o último elemento do mesmo objeto de matriz, a expressão de ponteiroQ+1 compara maior queP. Em todos os outros casos, o comportamento éIndefinido.

A ênfase é minha.

Os argumentosdst esrc pode ser convertido em ponteiros parachar para aliviar problemas estritos de alias, mas é possível comparar dois ponteiros que podem apontar para dentro de blocos diferentes, para fazer a cópia na ordem correta, caso apontem para dentro do mesmo bloco?

A solução óbvia éif (src < dst), mas isso é indefinido sesrc edst aponte para blocos diferentes. "Indefinido" significa que você não deve nem assumir que a condição retorna 0 ou 1 (isso seria chamado de "não especificado" no vocabulário do padrão).

Uma alternativa éif ((uintptr_t)src < (uintptr_t)dst), que é pelo menos não especificado, mas não tenho certeza de que o padrão garanta que, quandosrc < dst é definido, é equivalente a(uintptr_t)src < (uintptr_t)dst). A comparação de ponteiro é definida a partir da aritmética do ponteiro. Por exemplo, quando leio a seção 6.5.6, parece-me que a aritmética do ponteiro pode ir na direção oposta àuintptr_t aritmética, isto é, que um compilador compatível possa ter, quandop é do tipochar*:

((uintptr_t)p)+1==((uintptr_t)(p-1)

Este é apenas um exemplo. De um modo geral, muito pouco parece ser garantido ao converter ponteiros em números inteiros.

Esta é uma questão puramente acadêmica, porquememmove é fornecido junto com o compilador. Na prática, os autores do compilador podem simplesmente promover uma comparação indefinida de ponteiros com comportamentos não especificados ou usar o pragma relevante para forçar seu compilador a compilar seusmemmove corretamente. Por exemplo,esta implementação tem este trecho:

if ((uintptr_t)dst < (uintptr_t)src) {
            /*
             * As author/maintainer of libc, take advantage of the
             * fact that we know memcpy copies forwards.
             */
            return memcpy(dst, src, len);
    }

Eu ainda gostaria de usar este exemplo como prova de que o padrão vai longe demais com comportamentos indefinidos, se é verdade quememmove não pode ser implementado com eficiência no padrão C. Por exemplo, ninguém assinalou ao responderesta pergunta SO.

questionAnswers(5)

yourAnswerToTheQuestion