Файловые операции в C на разных архитектурах

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

Рабочий процесс следующий:

  • пользователь открывает файл с нашим API (или со стандартным fopen). Оба возвращаются FILE*, Файл открыт в двоичном режиме!
  • пользователь записывает данные в файл, используя стандартные команды библиотеки (например, fwrite)
  • Пользователь открывает транзакцию по открытому файлу с помощью нашего API: TRANSACTION a = trans_start(FILE* )
  • пользователь устанавливает валидаторы данных для TRANSACTION объект (set_validator(TRANSACTION, int(*)(char*))
  • пользователь «записывает» данные в файл, используя наш собственный API (int trans_write_string(TRANSACTION*, char*, length)
    • в действительности эта «запись» помещает свои данные в память для определенных выше валидаторов, которые могут выполнять операции с данными и устанавливать где-то некоторые флаги … не относящиеся к вопросу.
  • пользователь использует trans_commit(TRANSACTION) для того, чтобы на самом деле записать данные в файл. Теперь, в зависимости от флагов, установленных валидаторами, это может НЕ записывать данные в файл, а сообщать пользователю об ошибке (которая может быть исправлена ​​программно. … не так важна для вопроса).
  • пользователь закрывает файл, используя стандартный API fclose,

До сих пор у нас есть только метод обработки строк API (trans_write_string), который работает хорошо. Он создает свой собственный в буфере данных памяти, при необходимости изменяет содержимое, вызывает валидаторы и т. Д. При последовательных вызовах добавляет новые данные в буфер внутренней памяти, обрабатывает распределение и т. Д., И при успешном коммите записывает данные в файл с помощью fwrite (Да, это в основном проект C, однако ответы C ++ также не будут исключены).

Но теперь мы хотим (… должны) расширить API, чтобы иметь возможность записывать также числа (16-битные, 32-битные, 64-битные) и тоже плавающие … очень похоже на стандарт C API-интерфейс stdio делает это. Используя уже существующую реализацию для строки, это предполагает, что у нас есть буфер данных в памяти, который содержит N байтов символов (сама строка), нам может потребоваться 2 байта для 16-битного значения, а затем другое M байты для другой строки, 8 байтов для 64-битного значения, 2 байта для 16-битного значения и т. д.

Мы застряли в точке «как представить число в файле, чтобы его мог прочитать кто-то другой, кто использует другой компьютер / архитектура / os / endianness».

Вставка числа в поток памяти теоретически возможна посредством приведения к типу char (char* addr = &my_16bit_int) и место *(addr) а также *(addr + 1) на требуемый адрес (то есть: после N символы строки) и запись его в файл также возможна, но что если я захочу прочитать полученный файл в другой архитектуре, где endiannes отличается? А что, если «другой» компьютер — это всего лишь 16-битный кусок металла? Что произойдет в этом случае с 64-битными значениями, записанными в файле?

Какие хорошие практики существуют для решения такого рода проблем?

РЕДАКТИРОВАТЬ: Целевой файл должен быть двоичным, к нему будет добавлен текстовый файл (XML), описывающий его формат (например: N 8-байтовые символы, 1 16-битное значение и т. Д.) (Этот текстовый файл создается на основе результатов наших любимых валидаторов). Валидатор «говорит» что-то вроде: ДА, я принимаю это 16-битное значение, нет, я отклоняю эту длинную строку и т. Д. … и кто-то еще создает формат данных XML на основе этого «вывода».

EDIT2Да, нам нужно обмениваться файлами на разных платформах, даже в огромных 20-летних коробках размером с холодильник 🙂

EDIT3Да, нам тоже нужно плавать!

4

Решение

Литье не достаточно, я думаю, метод сокетов htons а также htonl будет достаточным решением для int16 и int32. для int64 вы должны собрать его самостоятельно, поскольку официального метода не существует:

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

typedef union{
unsigned char c[2];
unsigned short s;
}U2;

//you can use the standard htons or this
unsigned short htons(unsigned short s)
{
U2 mask,res;
unsigned char* p = (unsigned char*)&s;
mask.s = 0x0001;
res.c[mask.c[0]] = p[0];
res.c[mask.c[1]] = p[1];
return res.s;
}

//the same for 4 bytes
typedef union{
unsigned char c[4];
unsigned short s[2];
unsigned long l;
}U4;

//you can use the standard htonl or this
unsigned long htonl(unsigned long l)
{
U4 mask,res;
unsigned char* p = (unsigned char*)&l;
mask.l = 0x00010203;
res.c[mask.c[0]] = p[0];
res.c[mask.c[1]] = p[1];
res.c[mask.c[2]] = p[2];
res.c[mask.c[3]] = p[3];
return res.l;
}

typedef union{
unsigned char c[8];
unsigned char c2[2][4];
unsigned short s[4];
unsigned long l[2];
unsigned long long ll;
}U8;

//for int64 you can use the int64 and do the same, or you can to do it with 2*4 like i did
//you can give a void pointer as well..
unsigned long long htonll(unsigned long long ll)//void htonll(void* arg, void* result)
{
U2 mask1;
U4 mask2;
U8 res;

unsigned char* p = (unsigned char*)≪ //or (unsigned char*)arg
mask1.s = 0x0001;
mask2.l = 0x00010203;
//I didn't use the int64 for convertion
res.c2[mask1.c[0]][mask2.c[0]] = p[0];
res.c2[mask1.c[0]][mask2.c[1]] = p[1];
res.c2[mask1.c[0]][mask2.c[2]] = p[2];
res.c2[mask1.c[0]][mask2.c[3]] = p[3];
res.c2[mask1.c[1]][mask2.c[0]] = p[4];
res.c2[mask1.c[1]][mask2.c[1]] = p[5];
res.c2[mask1.c[1]][mask2.c[2]] = p[6];
res.c2[mask1.c[1]][mask2.c[3]] = p[7];

//memcpy(result,res.c,8);
return res.ll;
}
//or if you want to use the htonl:
unsigned long long htonll2(unsigned long long ll)
{
U2 mask1;
U8 res;
mask1.s = 0x0001;
unsigned long* p = (unsigned long*)≪
res.l[0] = htonl(p[mask1.c[0]]);
res.l[1] = htonl(p[mask1.c[1]]);
return res.ll;
}

int main()
{
unsigned short s = 0x1122;
cout<<hex<<htons(s)<<endl;
unsigned long l = 0x11223344;
cout<<hex<<htonl(l)<<endl;
unsigned long long ll=0x1122334455667788;
cout<<hex<<htonll(ll)<<endl;
cout<<hex<<htonll2(ll)<<endl;
return 0;
}
2

Другие решения

Вы должны определить формат или выбрать существующий двоичный файл
форматировать как XDR, и читать и писать. Так, например, чтобы
написать 32-битное целое число в XDR:

void
write32Bits( FILE* dest, uint_least32_t value )
{
putc( (value >> 24) & 0xFF, dest );
putc( (value >> 16) & 0xFF, dest );
putc( (value >>  8) & 0xFF, dest );
putc( (value      ) & 0xFF, dest );
}

С плавающей точкой сложнее, но если вы готовы
ограничьте свои платформы теми, которые поддерживают IEEE float, вы можете
типа каламбур float в uint32_t а также double в uint64_t, а также
выведите его как беззнаковое целое. Точно так же, если вы ограничиваете
самостоятельно до 2-х дополнительных машин с 32-битным интегральным типом,
Вы также можете использовать процедуру сдвига и маски выше для подписанного
значения (и целочисленные типы будут uint32_t а также
int32_t).

Что касается портативности: я думаю, что IEEE универсален,
кроме мэйнфреймов, и дополнение 2 универсально, за исключением
для очень экзотических мейнфреймов. (Мэйнфреймы IBM являются дополнением 2,
но не IEEE. Мэйнфреймы Unisys не являются дополнением 2, а
не имеют 32-битного целочисленного типа. Я не уверен, что другое
мэйнфреймы все еще существуют, но в прошлом были
всякие экзотики.)

1

Если вы используете glibc, вы можете использовать его функции для <-> быть преобразованиями из «endian.h»:

SYNOPSIS
#define _BSD_SOURCE             /* See feature_test_macros(7) */
#include <endian.h>

uint16_t htobe16(uint16_t host_16bits);
uint16_t htole16(uint16_t host_16bits);
uint16_t be16toh(uint16_t big_endian_16bits);
uint16_t le16toh(uint16_t little_endian_16bits);

uint32_t htobe32(uint32_t host_32bits);
uint32_t htole32(uint32_t host_32bits);
uint32_t be32toh(uint32_t big_endian_32bits);
uint32_t le32toh(uint32_t little_endian_32bits);

uint64_t htobe64(uint64_t host_64bits);
uint64_t htole64(uint64_t host_64bits);
uint64_t be64toh(uint64_t big_endian_64bits);
uint64_t le64toh(uint64_t little_endian_64bits);

Если вы не используете glibc, вы можете просто взглянуть на glibc-2.18 / bits / byteswap.h

1
По вопросам рекламы [email protected]