Запись и чтение длинного значения int в коде C

StackOverflow https://stackoverflow.com/questions/1105938

  •  12-09-2019
  •  | 
  •  

Вопрос

Я работаю над форматом файла, который должен записываться и считываться в нескольких разных операционных системах и компьютерах.Некоторые из этих компьютеров должны быть машинами 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 (который, возможно, тоже ускорит мой код) вместо этого?

Это было полезно?

Решение

Используйте типы в 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);

Другие советы

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

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

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

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

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

Вы говорите, что вас не беспокоят другие платформы. еще.Я буду понимать, что вы хотите сохранить возможность их поддержки, и в этом случае вам следует определить порядок байтов вашего формата файла.x86 имеет прямой порядок байтов, поэтому вы можете подумать, что это лучший вариант.Но 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 или чего-то еще.Или использовать прямой порядок байтов, предполагая, что большинство ваших платформ будут с прямым порядком байтов, и поэтому на них скорость будет «в среднем» выше.В этом случае вам нужно будет написать или найти функции «от хоста к прямому порядку байтов» и «от прямого байта к хосту», которые, конечно, на x86 просто ничего не делают.

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

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

Вот функции для чтения и записи 32-битных целых чисел без знака из потока с учетом произвольного порядка байтов, заданного целым числом, представлением которого является последовательность байтов. 0,1,2,3: с порядком байтов.h, с порядком байтов.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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top