Pergunta

Eu estou trabalhando em um formato de arquivo que deve ser escrito e lido em vários sistemas operacionais diferentes e computadores. Alguns desses computadores deve ser x86 máquinas, outros x86-64. podem existir alguns outros processadores, mas eu não estou preocupado com eles ainda .

Este formato de arquivo deve conter vários números que seriam lidos como esta:

struct LongAsChars{
    char c1, c2, c3, c4;
};

long readLong(FILE* file){
    int b1 = fgetc(file);
    int b2 = fgetc(file);
    int b3 = fgetc(file);
    int b4 = fgetc(file);
    if(b1<0||b2<0||b3<0||b4<0){
        //throwError
    }

    LongAsChars lng;
    lng.c1 = (char) b1;
    lng.c2 = (char) b2;
    lng.c3 = (char) b3;
    lng.c4 = (char) b4;

    long* value = (long*) &lng;

    return *value;
}

e escrita como:

void writeLong(long x, FILE* f){
    long* xptr = &x;
    LongAsChars* lng = (LongAsChars*) xptr;
    fputc(lng->c1, f);
    fputc(lng->c2, f);
    fputc(lng->c3, f);
    fputc(lng->c4, f);
}

Embora este parece estar funcionando no meu computador, eu estou preocupado que ele não pode, em outros, ou que o formato de arquivo pode acabar sendo diferente entre computadores (32 bits vs computadores 64 bits, por exemplo). Estou fazendo algo errado? Como devo implementar meu código para usar um número constante de bytes por número?

Eu deveria usar fread (o que, eventualmente, fazer o meu código mais rápido também) em vez?

Foi útil?

Solução

Use os tipos em stdint.h para garantir que você obtenha o mesmo número de bytes dentro e para fora.

Em seguida, você está apenas deixou de lidar com questões endianness, que você código provavelmente não realmente pega.

Serializing longo com um caractere alias * deixa você com diferentes ordens de byte no arquivo escrito para plataformas com endianess diferente.

Você deve decompor o bytes algo assim:

char c1 = (val >>  0) & 0xff;
char c2 = (val >>  8) & 0xff;
char c3 = (val >> 16) & 0xff;
char c4 = (val >> 24) & 0xff;

e recompor em seguida, usando algo como:

val = (c4 << 24) |
      (c3 << 16) |
      (c2 <<  8) |
      (c1 <<  0);

Outras dicas

Você também pode executar em problemas com endianness . Por que não usar algo como NetCDF ou HDF , que cuidar de todos os problemas de portabilidade que possam surgir?

Ao invés de usar estruturas com personagens neles, considere uma fórmula matemática:

long l  = fgetc() << 24;
     l |= fgetc() << 16;
     l |= fgetc() <<  8;
     l |= fgetc() <<  0;

Este é um pouco mais direta e clara sobre o que você está tentando realizar. Ele também pode ser implementado em um loop para lidar com números maiores.

Você não quer usar long int. Que podem ser de diferentes tamanhos em diferentes plataformas, por isso é um non-starter para um formato independente de plataforma. Você tem que decidir o que gama de necessidades de valores a serem armazenados no arquivo. 32 bits é provavelmente mais fácil.

Você diz que não está preocupado com outras plataformas ainda . Vou levar isso para dizer que você quer manter a possibilidade de apoiá-los, caso em que você deve definir o byte-order do seu formato de arquivo. x86 é little-endian, assim você pode pensar que é o melhor. Mas big-endian é a ordem de intercâmbio "padrão" se alguma coisa é, uma vez que ele é usado em rede.

Se você vai para big-endian ( "rede byte ordem"):

// can't be bothered to support really crazy platforms: it is in
// any case difficult even to exchange files with 9-bit machines,
// so we'll cross that bridge if we come to it.
assert(CHAR_BIT == 8);
assert(sizeof(uint32_t) == 4);

{
    // write value
    uint32_t value = 23;
    const uint32_t networkOrderValue = htonl(value);
    fwrite(&networkOrderValue, sizeof(uint32_t), 1, file);
}

{
    // read value
    uint32_t networkOrderValue;
    fread(&networkOrderValue, sizeof(uint32_t), 1, file);
    uint32_t value = ntohl(networkOrderValue);
}

Na verdade, você nem precisa declarar duas variáveis, é apenas um pouco confuso para substituir "valor" com seu equivalente ordem de rede na mesma variável.

Ele funciona porque "rede byte ordem" é definido para ser o arranjo de bits resulta em uma ordem intercambiáveis ??(big-endian) na memória. Não há necessidade de confusão com uniões porque qualquer objecto armazenado em C pode ser tratada como uma sequência de carvão animal. Não há necessidade de caso especial para endianness porque é isso que ntohl / htonl são para.

Se isto é muito lento, você pode começar a pensar em fiendishly otimizado byte-swapping específico da plataforma, com SIMD ou qualquer outra coisa. Ou usando little-endian, no pressuposto de que a maioria de suas plataformas será little-endian e por isso é mais rápido "em média" entre eles. Nesse caso, você precisará escrever ou encontrar "host para little-endian" e "little-endian para host" funções, que é claro em x86 simplesmente não fazer nada.

Eu acredito que a maioria abordagem de arquitetura cruz é usar os tipos uintXX_t, conforme definido no stdint.h. página href="http://linux.die.net/man/3/uint32_t" rel="nofollow ver o homem aqui. Por exemplo, um int32_t lhe dará um número inteiro de 32 bits em x86 e x86-64. Eu usá-los por padrão agora em todo o meu código e não tiveram problemas, como eles são bastante normal em todos os * NIX.

Assumindo sizeof(uint32_t) == 4, existem 4!=24 ordens de byte possíveis, dos quais pouco-endian e big-endian são os exemplos mais proeminentes, mas outros têm sido utilizados, bem como (por exemplo, PDP-endian).

Aqui estão as funções para leitura e escrita de 32 bits inteiros sem sinal de um córrego, atendendo uma ordem byte arbitrário que é especificado pelo número inteiro cuja representação é o 0,1,2,3 seqüência de bytes: endian.h , endian.c

O cabeçalho define esses protótipos

_Bool read_uint32(uint32_t * value, FILE * file, uint32_t order);
_Bool write_uint32(uint32_t value, FILE * file, uint32_t order);

e essas constantes

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