Сериализация данных в C? - PullRequest
       14

Сериализация данных в C?

2 голосов
/ 10 апреля 2011

У меня есть эта структура, которую я хочу записать в файл:

typedef struct
{
    char* egg;
    unsigned long sausage;
    long bacon;
    double spam;
} order;

Этот файл должен быть двоичным и должен быть доступен для чтения на любой машине, которая имеет Компилятор C99.

Я рассмотрел различные подходы к этому вопросу, такие как ASN.1, XDR, XML, ProtocolBuffers и многие другие, но ни один из них не соответствует моим требованиям:

  • маленький
  • простой
  • написано в C

Тогда я решил создать свой собственный протокол данных. Я мог бы справиться с следующие представления целых типов:

  • без знака
  • подписано в дополнение
  • подписано дополнение 2
  • подписано знак и величина

действительным, простым и чистым способом (впечатляет, нет?). Тем не менее настоящих типов сейчас больно.

Как мне прочитать float и double из потока байтов? Стандарт говорит, что побитовые операторы (по крайней мере &, |, << и >>) предназначены для integer только типы, что оставило меня без надежды. Единственный способ, которым я мог думаю было:

int sign;
int exponent;
unsigned long mantissa;

order my_order;

sign = read_sign();
exponent = read_exponent();
mantissa = read_mantissa();

my_order.spam = sign * mantissa * pow(10, exponent);

но это не кажется эффективным. Я также не мог найти описание представления double и float. Как должен один продолжить до этого?

Ответы [ 5 ]

5 голосов
/ 10 апреля 2011

Если вы хотите быть максимально переносимым с помощью float, вы можете использовать frexp и ldexp:

void WriteFloat (float number)
{
  int exponent;
  unsigned long mantissa;

  mantissa = (unsigned int) (INT_MAX * frexp(number, &exponent);

  WriteInt (exponent);
  WriteUnsigned (mantissa);
}

float ReadFloat ()
{
  int exponent = ReadInt();
  unsigned long mantissa = ReadUnsigned();

  float value = (float)mantissa / INT_MAX;

  return ldexp (value, exponent);
}

Идея заключается в том, что ldexp, frexp и INT_MAX являются стандартными C. Кроме того, точностьдлина без знака обычно, по крайней мере, равна ширине мантиссы (нет гарантии, но это верное предположение, и я не знаю ни одной архитектуры, которая бы отличалась здесь).

Поэтому преобразование работает без потери точности.Деление / умножение с INT_MAX может потерять немного точности во время преобразования, но это компромисс, с которым можно жить.

2 голосов
/ 10 апреля 2011

Если вы используете C99, вы можете вывести действительные числа в переносимом шестнадцатеричном формате, используя %a.

2 голосов
/ 10 апреля 2011

Если вы используете IEEE-754, почему бы не получить доступ к float или double как unsigned short или unsigned long и сохранить данные с плавающей запятой в виде серии байтов, а затем повторно преобразовать «специализированный»unsigned short или unsigned long обратно к float или double на другой стороне передачи ... битовые данные будут сохранены, так что после передачи вы должны получить такое же число с плавающей запятой.

1 голос
/ 27 октября 2011

В этом ответе используется метод Нильса Пипенбринка, но я изменил несколько деталей, которые, как мне кажется, помогают обеспечить реальную переносимость C99. Это решение живет в воображаемом контексте, где encode_int64 и encode_int32 и т. Д. Уже существуют.

#include <stdint.h>     
#include <math.h>                                                         

#define PORTABLE_INTLEAST64_MAX ((int_least64_t)9223372036854775807) /* 2^63-1*/             

/* NOTE: +-inf and nan not handled. quickest solution                            
 * is to encode 0 for !isfinite(val) */                                          
void encode_double(struct encoder *rec, double val) {                            
    int exp = 0;                                                                 
    double norm = frexp(val, &exp);                                              
    int_least64_t scale = norm*PORTABLE_INTLEAST64_MAX;                          
    encode_int64(rec, scale);                                                    
    encode_int32(rec, exp);                                                      
}                                                                                

void decode_double(struct encoder *rec, double *val) {                           
    int_least64_t scale = 0;                                                     
    int_least32_t exp = 0;                                                       
    decode_int64(rec, &scale);                                                   
    decode_int32(rec, &exp);                                                     
    *val = ldexp((double)scale/PORTABLE_INTLEAST64_MAX, exp);                    
}

Это все еще не реальное решение, inf и nan не могут быть закодированы. Также обратите внимание, что обе части двойных несущих знаковых битов.

int_least64_t гарантируется стандартом (int64_t не ), и мы используем минимально допустимый максимум для этого типа для масштабирования двойного. Процедуры кодирования принимают int_least64_t, но для переносимости придется отклонить ввод, размер которого превышает 64 бита, то же самое для 32-битного случая.

1 голос
/ 10 апреля 2011

Стандарт C не определяет представление для типов с плавающей запятой. Лучше всего конвертировать их в формат IEEE-754 и хранить их таким образом. Переносимость двоичной сериализации типа double / float в C ++ может помочь вам в этом.

Обратите внимание, что стандарт C также не определяет формат для целых чисел. В то время как большинство компьютеров, с которыми вы, вероятно, столкнетесь, будут использовать нормальное представление с двумя дополнительными компонентами, для которого нужно иметь дело только с порядком байтов, также возможно, что они будут использовать представление с одним дополнительным набором или величиной знака, и как подписанные, так и без знака целые могут содержать отступ биты, которые не влияют на значение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...