Pergunta

Estou curioso sobre convenções para-trocadilhos tipo ponteiros / matrizes em C ++. Aqui é o caso de uso que tenho no momento:

Calcular uma soma de verificação simples de 32 bits ao longo de um sistema binário de dados, tratando-a como uma matriz de números inteiros de 32-bit (que sabemos o seu comprimento total é um múltiplo de 4), e, em seguida, soma-se todos os valores e ignorando transbordamento.

Eu esperaria uma função tão para ficar assim:

uint32_t compute_checksum(const char *data, size_t size)
{
    const uint32_t *udata = /* ??? */;
    uint32_t checksum = 0;
    for (size_t i = 0; i != size / 4; ++i)
        checksum += udata[i];
    return udata;
 }

Agora, a pergunta que eu tenho é, o que você considera a "melhor" maneira de data convertido ao udata?

C-estilo elenco?

udata = (const uint32_t *)data

C ++ elenco que assume todos os ponteiros são conversíveis?

udata = reinterpret_cast<const uint32_t *>(data)

C ++ fundido que entre tipos de ponteiro arbitrárias usando void* intermediário?

udata = static_cast<const uint32_t *>(static_cast<const void *>(data))

fundido através de uma união?

union {
    const uint32_t *udata;
    const char *cdata;
};
cdata = data;
// now use udata

Eu percebo perfeitamente que isso não será uma solução portátil de 100%, mas estou apenas esperando para usá-lo em um pequeno conjunto de plataformas onde eu sei que funciona (nomeadamente acessos à memória unaligned e suposições do compilador sobre aliasing ponteiro). O que você recomendaria?

Foi útil?

Solução

Quanto padrão do C ++ está em causa, litb ' s resposta é completamente correto e mais portátil. Fundição const char *data a um const uint3_t *, quer seja através de um elenco de estilo C, static_cast, ou reinterpret_cast, quebra as regras aliasing estritas (ver Entendimento Strict Aliasing ). Se você compilar com otimização plena, há uma boa chance de que o código não vai para a coisa certa.

Fundição através de uma união (como my_reint do litb) é provavelmente a melhor solução, embora não tecnicamente violar a regra de que, se você escrever para uma união através de um membro e lê-lo através de um outro, que resulta em um comportamento indefinido. No entanto, praticamente todos os compiladores suportar isto, e isso resulta no resultado esperado. Se você absolutamente desejo de acordo com o padrão 100%, ir com o método de deslocamento de bit. Caso contrário, eu recomendo ir com lançando através de uma união, que é susceptível de lhe dar um melhor desempenho.

Outras dicas

Ignorando a eficiência, a simplicidade de código que eu faria:

#include <numeric>
#include <vector>
#include <cstring>

uint32_t compute_checksum(const char *data, size_t size) {
    std::vector<uint32_t> intdata(size/sizeof(uint32_t));
    std::memcpy(&intdata[0], data, size);
    return std::accumulate(intdata.begin(), intdata.end(), 0);
}

Eu também gosto última resposta de litb, o que muda a cada caractere, por sua vez, só que desde caractere pode ser assinado, eu acho que precisa de uma máscara extra:

checksum += ((data[i] && 0xFF) << shift[i % 4]);

Quando o tipo de trocadilhos é um problema em potencial, eu prefiro não digite pun, em vez de tentar fazê-lo com segurança. Se você não criar qualquer ponteiros alias de tipos distintos, em primeiro lugar, então você não precisa se preocupar com o que o compilador pode fazer com aliases, e nem o programador de manutenção que vê suas múltiplas static_casts através de uma união.

Se você não quer alocar memória tanta extra, então:

uint32_t compute_checksum(const char *data, size_t size) {
    uint32_t total = 0;
    for (size_t i = 0; i < size; i += sizeof(uint32_t)) {
        uint32_t thisone;
        std::memcpy(&thisone, &data[i], sizeof(uint32_t));
        total += thisone;
    }
    return total;
}

otimização suficiente vai se livrar do memcpy ea variável uint32_t adicional inteiramente no gcc, e apenas ler um valor inteiro não alinhado, qualquer que seja a forma mais eficiente de fazer isso é na sua plataforma, em linha reta da matriz de origem. Eu espero que o mesmo é verdade para outros compiladores "sérios". Mas este código é agora maior do que o do litb, por isso não há muito a ser dito para ele que não seja o meu é mais fácil de se transformar em um modelo função que vai funcionar tão bem com uint64_t, e meu trabalha como endian-ness nativo em vez de pouco escolhendo -endian.

Este é, naturalmente, não totalmente portátil. Assume-se que a representação de armazenamento de sizeof (uint32_t) carboniza corresponde à representação de armazenamento de um uin32_t da maneira que queremos. Isso está implícito na pergunta, uma vez que afirma que um pode ser "tratado como" o outro. Endian-ness, seja um char é de 8 bits, e se uint32_t usa todos os bits em sua representação de armazenamento pode, obviamente, intrometer, mas a questão implica que eles não vão.

Não são os meus cinquenta centavos -. Diferentes maneiras de fazer isso

#include <iostream>
#include <string>
#include <cstring>

    uint32_t compute_checksum_memcpy(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            // memcpy may be slow, unneeded allocation
            uint32_t dest; 
            memcpy(&dest,data+i,4);
            checksum += dest;
        }
        return checksum;
    }

    uint32_t compute_checksum_address_recast(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            //classic old type punning
            checksum +=  *(uint32_t*)(data+i);
        }
        return checksum;
    }

    uint32_t compute_checksum_union(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            //Syntax hell
            checksum +=  *((union{const char* c;uint32_t* i;}){.c=data+i}).i;
        }
        return checksum;
    }

    // Wrong!
    uint32_t compute_checksum_deref(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            checksum +=  *&data[i];
        }
        return checksum;
    }

    // Wrong!
    uint32_t compute_checksum_cast(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            checksum +=  *(data+i);
        }
        return checksum;
    }


int main()
{
    const char* data = "ABCDEFGH";
    std::cout << compute_checksum_memcpy(data, 8) << " OK\n";
    std::cout << compute_checksum_address_recast(data, 8) << " OK\n";
    std::cout << compute_checksum_union(data, 8) << " OK\n";
    std::cout << compute_checksum_deref(data, 8) << " Fail\n";
    std::cout << compute_checksum_cast(data, 8) << " Fail\n";
}

Eu sei que esta discussão tem sido inativo por um tempo, mas pensei que eu ia postar uma rotina de fundição genérico simples para esse tipo de coisa:

// safely cast between types without breaking strict aliasing rules
template<typename ReturnType, typename OriginalType>
ReturnType Cast( OriginalType Variable )
{
    union
    {
        OriginalType    In;
        ReturnType      Out;
    };

    In = Variable;
    return Out;
}

// example usage
int i = 0x3f800000;
float f = Cast<float>( i );

Espero que ajude alguém!

Isto parece um exemplo de caso-book de quando usar reinterpret_cast, qualquer outra coisa lhe dará o mesmo efeito sem a explicitação você começa de usar um construtor de linguagem para seu uso oficial.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top