Feb 13, 2011 03:53
О структурах в Си.
Недавно по долгу дружбы пришлось разбираться в программе, в которой осуществлялся обмен пакетами по сети, а реализовывалось всё это следующим страшным образом:
struct packet0 {
char field0[20];
long field1;
char field2[4];
long field3;
};
struct packet1 {
int field0[4];
char field1[32];
char field2[4];
long field3;
};
union sender_t {
packet1 format1;
packet0 format0;
char raw[MAX_PACKET]
} sender;
...
...
// отправка пакета:
strcpy(sender.packet0.field0,"hello");
sender.packet0.field1 = 24;
strcpy(sender.packet0.field2,"wow");
sender.packet0.field4 = 0xDEADBEEF;
send(s,sender.raw,sizeof(packet0));
...
// приём пакета:
recv(s,sender.raw,MAX_PACKET);
cout << sender.format1.field0[2] << endl; // long
cout << sender.format1.field1 << endl; // char*
Любой человек, хоть сколько нибудь разбирающийся в С, понимает, насколько ужасен и непригоден к использованию такой подход. Но. Количество кода для подобных операций в разы, если не на порядки, меньше, чем требуется при нормальной сериализации. Если же протокол меняется, то объём работы здесь практически ничтожен, а вот при нормальной сериализации он колоссален.
Я ни в коем случае не призываю использовать подобное на практике (хотя, хотите верьте, хотите нет, оно работает, что неудивительно для одной ОС и одного компилятора). Мне было бы вполне достаточно если бы в С++ можно было ввести некую систему типов, ясно описывающую сериализацию того или иного поля протокола, и представить её декларативно. В грядущем Ноль-Иксе такие возможности нам дадут кортежи (таплы) и это хорошо. Но. Кортеж есть неименованный набор данных разного типа. Ключевое слово - неименованный. Сделать его именованным можно, но это сильные затраты в любом случае. Я специально опускаю тот факт, что кортежи ещё и имеют заранее заданный размер - в С++ это не критично (а вот, например, в Хаскелле, где кортеж каждого размера есть отдельный независимый тип и все функции перегружаются для каждого размера отдельно, очень даже).
В биткоде LLVM доступ к полям структур в модели кода (а код там всё таки сишный в основе) осуществляется... по константным индексам! Т.е. фактически структура есть что-то типа того самого кортежа с индексированным доступом и именованностью. И если бы подобный доступ был возможен непосредственно из Си(-плюсплюс?), мы бы могли реализовывать протоколы так же легко, как в подобных ужасных способах. Как-то так:
struct packet0 {
array< byte, 20 > field0;
dwordBE field1;
array< byte, 4 > field2;
dwordBE field3;
};
struct packet1 {
array< dwordBE, 4 > field0;
array< byte, 32 > field2;
array< byte, 4 > field3;
dwordLE field4;
};
serializator sender;
...
...
// отправка пакета:
packet0 toSend;
toSend.field0 = "hello";
toSend.field1 = 24;
toSend.field2 = wow;
toSend.field3 = 0xDEADBEEF;
packet pack = sender.serialize(toSend);
send(s,pack.get(),pack.size());
...
// приём пакета:
packet1 toRecv;
packet pack = sender.deserialize(toRecv);
recv(s,pack.get(),MAX_PACKET);
cout << toRecv.field0[2] << endl; // long
cout << toRecv.field1 << endl; // char*
Как реализовать сериализацию, и что хранить в классе packet, я оставлю на откуп читателю. Скажу лишь, что для этого нужны аналоги кортежных type_at::type и get_at(), с которыми, зная систему типов, работать было бы легко и приятно.
С++