LINUX.ORG.RU

Как всё-таки работать с сырыми байтами?

 , ,


0

2

Есть некий бинарный файл, хранит 32битные значения (signed и unsigned) и ascii строки. Как-то попадает в память (может ifstream::read, или mmap). Вопрос в том, как их него правильно читать значения не нарвавшись на undefined behavior, а желательно - и на implementation-specific behavior.

Для начала, просто unsigned и строки.

Вобщем-то, вопрос сводится к тому, в каком типе хранить данные. С одной стороны, тот же ifstream::read возвращает char*. С другой, std::string конструируется также из char*. Но с третьей, чтобы безопасно кросс-платформенно читать инты (побайтовое чтение + сдвиги + OR), нужен беззнаковый тип, потому что сдвиг знакового ЕМНИП UB. Каст знакового в беззнаковое, опять таки ЕМНИП, implementation defined. Остаётся только касты между unsigned char* и char*, что с ними - будет ли ub или implementation-defined? Если будет, что делать? Я больше вариантов не вижу.

Ну а с unsigned, как я понимаю, всё совсем сложно, ибо потенциально они могут быть представлены как-то иначе чем в дополнительном коде, значит из байтов их напрямую собирать нельзя (но в файле они в обычном дополнительном коде).

★★★★★

Если у тебя заранее неизвестно, что ты читаешь, то скорее всего ты что-то делаешь не так.

Ну а по факту, читаешь с помощью read() в буфер из unsigned char[], потом натравливаешь на него разные эвристики, чтобы определить, что ты прочитал и в зависимости от результата предпринимаешь дальнейшие действия.

DELIRIUM ☆☆☆☆☆
()

http://plan9.bell-labs.com/sys/doc/comp.html

Portability
Within Plan 9, it is painless to write portable programs, programs whose source is independent of the machine on which they execute. The operating system is fixed and the compiler, headers and libraries are constant so most of the stumbling blocks to portability are removed. Attention to a few details can avoid those that remain.
Plan 9 is a heterogeneous environment, so programs must expect that external files will be written by programs on machines of different architectures. The compilers, for instance, must handle without confusion object files written by other machines. The traditional approach to this problem is to pepper the source with #ifdefs to turn byte-swapping on and off. Plan 9 takes a different approach: of the handful of machine-dependent #ifdefs in all the source, almost all are deep in the libraries. Instead programs read and write files in a defined format, either (for low volume applications) as formatted text, or (for high volume applications) as binary in a known byte order. If the external data were written with the most significant byte first, the following code reads a 4-byte integer correctly regardless of the architecture of the executing machine (assuming an unsigned long holds 4 bytes):
ulong
getlong(void)
{
    ulong l;
    l = (getchar()&0xFF)<<24;
    l |= (getchar()&0xFF)<<16;
    l |= (getchar()&0xFF)<<8;
    l |= (getchar()&0xFF)<<0;
    return l;
}
Note that this code does not ‘swap’ the bytes; instead it just reads them in the correct order. Variations of this code will handle any binary format and also avoid problems involving how structures are padded, how words are aligned, and other impediments to portability. Be aware, though, that extra care is needed to handle floating point data.
Efficiency hounds will argue that this method is unnecessarily slow and clumsy when the executing machine has the same byte order (and padding and alignment) as the data. The CPU cost of I/O processing is rarely the bottleneck for an application, however, and the gain in simplicity of porting and maintaining the code greatly outweighs the minor speed loss from handling data in this general way. This method is how the Plan 9 compilers, the window system, and even the file servers transmit data between programs.
To port programs beyond Plan 9, where the system interface is more variable, it is probably necessary to use pcc and hope that the target machine supports ANSI C and POSIX.

про

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

оставь это на будущее когда сталкнёшся.достаточно отделить внешнее от внутреннего представления, единообразным вводом-выводом.

qulinxao ★★☆
()
Последнее исправление: qulinxao (всего исправлений: 1)

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

31      float UniModbus::getfloat(QByteArray &package)
32	{
33	    typedef union
34	    {
35	        float value;
36	        uchar m[4];
37	    } conv;
38	
39	    conv converter;
40	    converter.m[0] = QString(package.mid(0, 2)).toInt(0, 16);
41	    converter.m[1] = QString(package.mid(2, 2)).toInt(0, 16);
42	    converter.m[2] = QString(package.mid(4, 2)).toInt(0, 16);
43	    converter.m[3] = QString(package.mid(6, 2)).toInt(0, 16);
44	
45	    package = package.mid(8);
46	
47	    return converter.value;
48	}
49	
50	int UniModbus::getint(QByteArray &package)
51	{
52	    typedef union
53	    {
54	        int   value;
55	        uchar m[4];
56	    } conv;
57	
58	    conv converter;
59	    converter.m[0] = QString(package.mid(6, 2)).toInt(0, 16);
60	    converter.m[1] = QString(package.mid(4, 2)).toInt(0, 16);
61	    converter.m[2] = QString(package.mid(2, 2)).toInt(0, 16);
62	    converter.m[3] = QString(package.mid(0, 2)).toInt(0, 16);
63	
64	    package = package.mid(8);
65	
66	    return converter.value;
67	}
68	
69	QString UniModbus::getstring(QByteArray &package)
70	{
71	    QString result;
72	
73	    while (package.length() > 0)
74	    {
75	        quint8 symbol = getchar(package);
76	        if (symbol)
77	            result.append(symbol);
78	        else
79	            break;
80	    }
81

sambist ★★
()
Ответ на: комментарий от alegz

Сдвиг влево — не UB.

Если бит знака установлен, то сдвиг влево == UB. Вроде так.

pathfinder ★★★★
()

Если у тебя в файле MSB, то не морочь голову и пользуй системную ntohl()

Если у тебя в файле LSB, то не морочь голову, посмотри как сделана ntohl() и сделай так же, только наоборот.

Stanson ★★★★★
()

Какая-то у тебя каша. Открой парсер любого сетевого протокола для примера.

four_str_sam
()
Ответ на: комментарий от sambist

Эмм, я правильно понял, что у вас там флоаты сериализуются побайтово в строчку вида «DEADBEEF»? Вы там вконец укурились в своём продакшене?

Gvidon ★★★★
()
Ответ на: комментарий от anonymous

Единственное, что мне не понятно, так это зачем он делает вот это:

package = package.mid(8);

и ссылка в параметрах должна быть константной.
А как он в modbus'e float'ы преобразовывает - я не в курсе, в алгоритм не вникал.

UVV ★★★★★
()
Ответ на: комментарий от UVV

Единственное, что мне не понятно, так это зачем он делает вот это:

package = package.mid(8);

Данные считываются только раз, массив после считывания режется, позволяет не хранить снаружи счетчик текущей позиции.

sambist ★★
()
Ответ на: комментарий от sambist

Так есть же remove, нафига ты весь массив ещё раз копируешь? Причём при каждом преобразовании.

UVV ★★★★★
()
Последнее исправление: UVV (всего исправлений: 1)

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

anonymous
()
Ответ на: комментарий от Gvidon

В автоматизации и автоиндустрии используется.

UVV ★★★★★
()

А использовать что-то вменяемое для сериализации-десериализации типа protobuf?

g0t0
()
Ответ на: комментарий от DELIRIUM

Сдвиг влево — не UB. Кури стандарт, потом приходи

Это:

Если бит знака установлен, то сдвиг влево == UB.

Если у тебя заранее неизвестно, что ты читаешь, то скорее всего ты что-то делаешь не так.

Известно. Я спросил другое.

Пример того, что используется в реальном продакшене

Не смешно.

то не морочь голову и пользуй системную ntohl()

Байты я уж как-нибудь переставлю. Вопрос как-бы был намного шире и про другое.

Какая-то у тебя каша. Открой парсер любого сетевого протокола для примера.

Пример в студию.

А использовать что-то вменяемое для сериализации-десериализации типа protobuf?

Нет. Условия я обозначил.

slovazap ★★★★★
() автор топика
Ответ на: комментарий от anonymous

как тебе оно поможет распарсить существующий протокол?

А если говорить о создании своего бинарного протокола, то кроме protobuf ничего актуальнее сейчас нет.

Deleted
()
Ответ на: комментарий от Deleted

Да не нужен мне свой протокол, мне нужно разбирать объективно существующий бинарный файл.

slovazap ★★★★★
() автор топика
Ответ на: комментарий от Deleted

А если говорить о создании своего бинарного протокола, то кроме protobuf ничего актуальнее сейчас нет.

Это, кстати, не правда. protobuf - не серебряная пуля. У него кошмарный оверхед, отвратительная производительность при работе с большими сообщениями, нет встроенной работы с потоками (т.е. в tcp, например, protobuf-сообщения приходится руками паковать в TLV) ублюдский интерфейс и необходимость во внешнем компиляторе.

Я бы лично его использовал только в двух случаях - когда возможно значительное расширение протокола при необходимости поддержки совместимости со старыми версиями (т.е. когда не было, очевидно, стадии проектирования вообще), или когда вообще на всё наплевать и нужно тяп-ляп сделать хоть что-то (т.е. тоже детский сад). Если эффективность не на последнем месте и есть возможность согласовать заранее версию, что угодно будет лучше. flatbuffers от того же гугла или да, ASN.1.

slovazap ★★★★★
() автор топика
Ответ на: комментарий от anonymous

всё давно придумали

Ах, лавсанчик, прежде чем пойти по ссылке, так-как тред про С++, я понадеялся, что там не будет Лиспа.

используется аналог

Даешь больше аналогов. Велосипед на Лиспе мы будем называть «Лисапед». Боольше лисапедов и маленьких лисапедиков.

pathfinder ★★★★
()

«Кастуй» через memcpy.

int read_int(char **ptr) {
    int ret;
    memcpy(&ret, *ptr, sizeof(ret));
    *ptr += sizeof(ret);
    return ret;
}

double read_double(char **ptr) {
    double ret;
    memcpy(&ret, *ptr, sizeof(ret));
    *ptr += sizeof(ret);
    return ret;
}
i-rinat ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.