Запись и чтение длинного значения int в коде C - PullRequest
2 голосов
/ 09 июля 2009

Я работаю над форматом файла, который должен быть записан и прочитан в нескольких различных операционных системах и компьютерах. Некоторые из этих компьютеров должны быть машинами x86, другие x86-64. Некоторые другие процессоры могут существовать, но я не беспокоюсь о них пока .

Этот формат файла должен содержать несколько чисел, которые будут читаться так:

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

и записывается как:

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

Хотя это, кажется, работает на моем компьютере, я обеспокоен тем, что это может не сработать на других компьютерах, или что формат файла может оказаться различным на разных компьютерах (например, 32 бита против 64-битных компьютеров). Я делаю что-то неправильно? Как мне реализовать свой код, чтобы использовать постоянное число байтов на число?

Должен ли я просто использовать fread (что, возможно, ускорит мой код) вместо этого?

Ответы [ 6 ]

8 голосов
/ 09 июля 2009

Используйте типы в stdint.h, чтобы гарантировать, что вы получаете и выводите одинаковое количество байтов.

Тогда у вас остались проблемы с порядком байтов, которые вы, вероятно, не пишете действительно справиться.

Сериализация long с псевдонимом char * оставляет вам различные порядки байтов в письменном файле для платформ с различным порядком байтов.

Вы должны разложить байты примерно так:

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

И затем перекомпоновать, используя что-то вроде:

val = (c4 << 24) |
      (c3 << 16) |
      (c2 <<  8) |
      (c1 <<  0);
1 голос
/ 10 июля 2009

Вы не хотите использовать long int. Это может быть разных размеров на разных платформах, так что это не стартер для независимого от платформы формата. Вы должны решить, какой диапазон значений должен быть сохранен в файле. 32 бита, вероятно, проще всего.

Вы говорите, что не беспокоитесь о других платформах пока . Я буду считать, что это означает, что вы хотите сохранить возможность их поддержки, и в этом случае вы должны определить порядок байтов вашего формата файла. x86 имеет младший порядок, так что вы можете подумать, что это лучше. Но big-endian - это «стандартный» порядок обмена, если он есть, поскольку он используется в сети.

Если вы идете для big-endian («порядок сетевых байтов»):

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

На самом деле вам даже не нужно объявлять две переменные, просто немного сбивает с толку, заменяя «значение» его сетевым эквивалентом в той же переменной.

Это работает, потому что «сетевой порядок байтов» определен так, чтобы любая последовательность битов приводила к взаимозаменяемому (старшему) порядку в памяти. Нет необходимости связываться с объединениями, потому что любой сохраненный объект в C может рассматриваться как последовательность символов. Нет необходимости в специальном случае для порядка байтов, потому что это то, для чего нужны ntohl / htonl.

Если это слишком медленно, вы можете начать думать о чёртово оптимизированной подстановке байтов для конкретной платформы, с SIMD или чем-то ещё. Или используя little-endian, исходя из предположения, что большинство ваших платформ будут с прямым порядком байтов, и поэтому они быстрее «в среднем» по ним. В этом случае вам нужно написать или найти функции «от хоста до байтов» и «от байтов до хоста», которые, конечно, в x86 просто ничего не делают.

1 голос
/ 09 июля 2009

Вместо того, чтобы использовать структуры с символами в них, рассмотрим более математический подход:

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

Это немного более прямо и ясно о том, чего вы пытаетесь достичь. Это также может быть реализовано в цикле для обработки больших чисел.

1 голос
/ 09 июля 2009

Вы также можете столкнуться с проблемами с endianness . Почему бы просто не использовать что-то вроде NetCDF или HDF , которое решает любые проблемы с переносимостью, которые могут возникнуть?

0 голосов
/ 10 июля 2009

Предполагая sizeof(uint32_t) == 4, существует 4!=24 возможных порядков байтов, из которых порядки с прямым порядком байтов и байты с прямым порядком байтов являются наиболее яркими примерами, но также использовались и другие (например, PDP-endian).

Здесь представлены функции для чтения и записи 32-битных целых чисел без знака из потока с учетом произвольного порядка байтов, который задается целым числом, представление которого представляет собой последовательность байтов 0,1,2,3: endian.h , endian.c

Заголовок определяет эти прототипы

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

и эти константы

LITTLE_ENDIAN
BIG_ENDIAN
PDP_ENDIAN
HOST_ORDER
0 голосов
/ 10 июля 2009

Я считаю, что наиболее кросс-архитектурный подход заключается в использовании типов uintXX_t, как определено в stdint.h. См. Справочную страницу здесь. Например, int32_t даст вам 32-битное целое число на x86 и x86-64. Теперь я использую их по умолчанию во всем своем коде, и у меня не было проблем, так как они довольно стандартны для всех * NIX.

...