Ghost2
> И почему он будет разный?
Может и не будет. Зависит от того, по какой границе компилятор выравнивает
Как это уже стандарт регламентирует компиляцию под x86 и x64 можешь рассказать подробнее, а на уровне мой компилятор откомпилил так, значит так мне не интересно. Но я наверняка не все тонкости знаю
ZeroCool++
Любой компилятор выравнивает размер так, чтобы в массиве структур все поля имели естественное выравнивание.
ZeroCool++
> Но исторически сложилось, что сетевой порядок байт big endian, а не little endian.
Зачем тащить наследие древних низкоуровневых протоколов типа TCP на уровень приложения?
Свой протокол надо разрабатывать исходя из современных требований, не оглядываясь на всякое старье.
Вон, HTTP, вообще, текстовым был до недавнего времени.
Ramm
> Одно и то же число при байтовом выводе в случае с копированием памяти и
> объединением выглядит по-разному.
> 255 в случае с memcpy_s выглядит как -1 0 0 0, а в bytes union: 255 0 0 0 (при
> переводе в char[])
> 1. Почему так происходит?
char, signed char и unsigned char - это три разных типа.
Двоичное представление в обоих случаях одинаковое - {0xff, 0, 0, 0}, только в первом случае 0xff интерпретируется как знаковое целое, а во втором - как беззнаковое.
> 2. Какую форму лучше использовать для передачи чисел по сети? Прогонять их
> через объединение или копирование памяти с дальнейшим получением массивов
> char?
Без разницы, в большинстве случаев после оптимизаций конечный код будет одинаковым.
> 3. union u перестанет существовать сразу после выхода из функции и память
> освободится?
union u - это описание типа, а не объект. Он не занимает места в памяти, а только описывает одну из интерпретаций последовательности байт.
То, что занимает память - это u p. Поскольку p - это локальная переменная функции, то её место освобождается автоматически при выходе из функции.
Zab
> Не удивляйся, если услышишь отзывы типа "ошибка в ДНК", мы тут от этого
> удерживаемся, потому как оно не продуктивно, другие могут не удержаться.
Я просто помню, как сам подобное писал, лет эдак десять назад, поэтому включаю оптимизм и верю, что если человек старается - то, однажды, он тоже сможет прийти к успеху.
Ramm
> Как же все-таки правильно переводить float-int-double в char для передачи через
> сокеты? Вот как ты переводишь?
Если прям совсем-совсем правильно - то битовой арифметикой. C++-компилятор реализует её максимально быстро, и она всегда даст правильный порядок, потому что порядок задан в коде явно.
Ramm
> Можно немного поподробнее, что значит "знать двоичную структуру"
В памяти компьютера, все данные представлены в виде последовательности байт.
Каждый объект в программе - это кусочек памяти определённого размера с описанием, как интерпретировать байты в этой памяти.
Например, если ты объявишь
struct person { person* next; int age; float weight; char name[256]; };
то это опишет компилятору тип "person" следующим образом:
- next: адрес в памяти, указывающий на объект типа "person", расположен в самом начале объекта;
- age: знаковое целое, расположено спустя 8 байт с начала объекта;
- weight: вещественное одинарное, расположено спустя 12 байт;
- name: массив char длиной в 256 элементов, расположен спустя 16 байт;
- общий размер структуры: 272 байта.
Затем, когда ты работаешь с этой структурой - например, делаешь
float weightinpounds(person const& p) { return p.weight * 2.20462f; }
это описание говорит компилятору о том, какие именно инструкции нужно передать процессору, чтобы получить тот же результат, который описывает эта функция.
| weightinpounds(person const&): | ; когда другие места программы обращаются к имени "weightinpounds(person const&)" - подставить в это имя вот этот адрес |
| ; по принятому компилятором соглашению, вызывающий функцию код передаёт аргумент p через регистр rdi, а результат ожидает в регистре xmm0 | |
| movss xmm0, DWORD PTR .LC0[rip] | ; загрузить в xmm0 вещественное одинарной точности, расположенное по адресу, обозначенному именем ".LC0" |
| mulss xmm0, DWORD PTR [rdi+12] | ; домножить число в xmm0 на вещественное одинарной точности, расположенное по адресу rdi + 12 |
| ret | ; вернуться в место, из которого была вызвана эта функция |
| .LC0: | ; когда другие места программы обращаются к имени ".LC0" - подставить в это имя вот этот адрес |
| .long 1074600062 | ; число 2.20462, представленное в виде вещественного одинарной точности |
_
А теперь - касательно господ знатоков.
ShadowTeolog
> char - int8_t
В общем случае - нет. Назначение char, во-первых - это символ однобайтовой кодировки (какой угодно на усмотрение реализации), во-вторых - это абстрактный тип для минимальной ячейки памяти, тип char* - единственный, которым можно алиасить другие объекты и при этом не нарваться на UB.
Если тебе нужно знаковое целое минимального размера - используй signed char.
ShadowTeolog
> union/struct без #pragma pack(push,1) это одно сплошное западло
Для union нет понятия "упаковка", потому что в нём по определению все поля кладутся по смещению ноль.
ShadowTeolog
> формат внутри float/double на разных архитектурах известен лишь богам.
Формат внутри float/double - это implementation-defined, что означает - открываешь документацию и читаешь.
На всех x86/x64-машинах - это IEEE 754 binary32 и binary64.
Но если у человека нет никакого желания учиться, то можно умеющих читать называть богами. Я разрешаю. c:
Delfigamer
Т.е. если я решил передавать по сети только float-ы, то единственная проблема при получении пакета (пускай это будет UDP), это определение, в каком порядке машина отправителя записала байты (char-ы в нашем случае) в big-endian или little-endian. Так? Их нужно правильно прочитать. Что плохого в моем плане, если каждый пакет начинать с одного и того же числа, на всех своих клиентах и серверах, например 255, а потом пробовать его получить, и если не выходит, то менять порядок байтов на противоположный (в каждом из четырех байтов). И следовательно все остальные числа представлены в обратном порядке...
Внутри пакета числа же не могут перемешаться=)
Ну, т.е. машина записала 2 числа и начальное число в пакет и отправила.
А мы получили:
b1_1, b1_2, b1_3, b1_4, b2_1, b2_2, b2_3, b2_4, b3_1, b3_2, b3_3, b3_4.
Первые 4 байта - это наш "флаг", пробуем собрать из них float, если получается 255, то и из других собираем в том же порядке, если не выходит, то пробуем получить 255 из b1_4, b1_3, b1_2, b1_1. И все должно работать, если это пакет от нашего клиента, а не случайный левый пакет, то 255 получится либо из b1_1, b1_2, b1_3, b1_4, либо из b1_4, b1_3, b1_2, b1_1. Так?
Ну и остальные числа получаем либо по прямому, либо по обратному положению байтов, т.е. если 255 это b1_4, b1_3, b1_2, b1_1, то 2 число нужно собирать из b2_1, b2_4, b2_3, b2_2, b2_1, а третье - b3_4, b3_3, b3_2, b3_1.
Повторюсь, если 255 получилось из b1_1, b1_2, b1_3, b1_4, то ничего не трогаем и получаем числа из байтов в том порядке, в котором они идут в пакете.
Чтобы не наткнуться на такую ситуацию, что собрали 254.999999999 или 255.000000001, float-ы опасно сравнивать, мы округляем float по целого, и записываем результат в int.
Я правильно понял?
Ramm
> Чтобы не наткнуться на такую ситуацию, что собрали 254.999999999 или
> 255.000000001, float-ы опасно сравнивать, мы округляем float по целого, и
> записываем результат в int.
Если число должно быть целым - передавай его целым.
Каждый байт способен принимать одно из 28 состояний. Соответственно, четыре байта - 24*8 = 232 уникальных значений.
int32_t присваивает каждому из этих состояний одно целое число, поэтому в нём можно сохранить любое целое на интервале от -231 до 231-1, включая ноль.
float же отдаёт значительную часть этих состояний на представление дробных чисел, поэтому оставшихся целых хватает только на интервал от -224 до 224.
Ramm
> Что плохого в моем плане, если каждый пакет начинать с одного и того же числа,
> на всех своих клиентах и серверах, например 255, а потом пробовать его
> получить, и если не выходит, то менять порядок байтов на противоположный (в
> каждом из четырех байтов).
В принципе - ничего плохого. Некоторые так и делают.
Ramm
> пускай это будет UDP
Нам стоит рассказывать про ненадёжность UDP, или ты уже в курсе? Что сообщение, посланное отправителем единожды, может быть получено адресатом любое число раз от нуля до бесконечности.
Delfigamer
> Нам стоит рассказывать про ненадёжность UDP, или ты уже в курсе? Что сообщение,
> посланное отправителем единожды, может быть получено адресатом любое число раз
> от нуля до бесконечности.
Нет-нет, я в курсе)
Delfigamer
> оставшихся целых хватает только на интервал от -2^24 до 2^24.
2^25 - мне этого за глаза хватит, зачем нужно больше?=)
Delfigamer
> Если число должно быть целым - передавай его целым.
В этом случае нужно ли для целого числа тоже держать int-маркер? После 255.0 еще и 255, например, и смотреть на порядок байтов в нем?
Ramm
> В этом случае нужно ли для целого числа тоже держать int-маркер? После 255.0
> еще и 255, например, и смотреть на порядок байтов в нем?
Специфицируй как байты идут в сети, пусть идут всегда одинаково. А если с какой стороны порядок другой, его обязанность конвертировать это в сетевой формат. Не надо никаких маркеров в сообщении.
Ramm
> > Нам стоит рассказывать про ненадёжность UDP, или ты уже в курсе? Что сообщение,
> > посланное отправителем единожды, может быть получено адресатом любое число раз
> > от нуля до бесконечности.
> Нет-нет, я в курсе)
А то, что обгонять друг друга в пути сообщения могут в курсе?
А то, что на твой сокет могут свалиться сообщения из других источников, не твоих, вообще неизвестного тебе формата? По ip-адресу источника их можно отфильтровать, конечно, но если это не злоумышленник, он и адрес подделает.
А что адрес на лету меняться может тоже знаешь?
Delfigamer
А теперь - касательно господ знатоков.
>ShadowTeolog
>> char - int8_t
>В общем случае - нет. Назначение char, во-первых - это символ однобайтовой кодировки (какой угодно на усмотрение реализации), во-вторых - это абстрактный тип для минимальной ячейки памяти, тип char* - единственный, которым можно алиасить другие >объекты и при этом не нарваться на UB.
>Если тебе нужно знаковое целое минимального размера - используй signed char.
Нужно знаковое целое размером восемь бит, и это оно и есть, размер char/signed char - на воле компилятора. И именно для этого было введено. UB-проблема программиста, в протоколе все должно быть четко.
>ShadowTeolog
>> union/struct без #pragma pack(push,1) это одно сплошное западло
>Для union нет понятия "упаковка", потому что в нём по определению все поля кладутся по смещению ноль.
Великолепно, теперь вспомним что внутри юниона у нас структура для распаковки, а ей выравнивание очень даже требуется, ни разу не использовал юнион ни для чего другого, да и то редко
>ShadowTeolog
>> формат внутри float/double на разных архитектурах известен лишь богам.
>Формат внутри float/double - это implementation-defined, что означает - открываешь документацию и читаешь.
>На всех x86/x64-машинах - это IEEE 754 binary32 и binary64.
>Но если у человека нет никакого желания учиться, то можно умеющих читать называть богами. Я разрешаю. c:
Отлично, но если речь идет только о x86/x64, то о чем тут вообще разговор, мне то казалось что упаковка должна была быть переносимой? Если ограничивать себя именно x86/x64 то зачем вообще эти танцы, достаточно макроса для интов MAKE_ENDIANLESS на входе, которые есть везде.
Zab
> А то, что обгонять друг друга в пути сообщения могут в курсе?
Да. Отправляешь 1 сообщение, 2 сообщение, 3 сообщение, 4 сообщение, а приходят 1, 3, 3, 2 .
Zab
> А то, что на твой сокет могут свалиться сообщения из других источников, не
> твоих, вообще неизвестного тебе формата? По ip-адресу источника их можно
> отфильтровать, конечно, но если это не злоумышленник, он и адрес подделает.
Можно проверять IP и порт. Можно еще добавить информацию в сообщение, типа ключа, которую передать клиенту при первом подключении.
Zab
> А что адрес на лету меняться может тоже знаешь?
В таком случае считать, что клиент ливнул и требовать нового подключения...
Ramm
> В таком случае считать, что клиент ливнул и требовать нового подключения...
Не всегда такое допустимо. Не знаю есть ли такие сейчас, а лет десять назад многие мобильные устройства норовили адрес менять каждые минут 5-15. Стационарные подключения чаще пары раз в сутки обычно адрес не меняют, но и при этом дисконнектить не всегда правильно, от задач зависит.
ShadowTeolog
> достаточно макроса для интов MAKE_ENDIANLESS на входе, которые есть везде.
Your search - MAKE_ENDIANLESS - did not match any documents.
Suggestions:
Везде, кроме интернета? 
ShadowTeolog
> теперь вспомним что внутри юниона у нас структура для распаковки
Ну так на структуру упаковку и вешай.
Или имелось в виду
> {union/{struct без #pragma pack(push,1)}}
? Тогда окей, значит, это я неправильно предложение распарсил.
ShadowTeolog
> Отлично, но если речь идет только о x86/x64, то о чем тут вообще разговор?
Я не знаю. Кто там в порядок байтов въелся? Вон, даже профессор Улыбочка уже не выдержал:
}:+()___ [Smile]
> вы наркоманы, никто уже не пользуется big-endian
Единственная BE-платформа с интернетом, о которой я знаю - это PS3. На неё никто прогать уже не даст, так что о ней волноваться смысла нет.
PS4/XBox - они на обычном x86.
Когда я в последний раз проверял - ARM на телефонах тоже работал в LE-режиме.
Ramm
> считать, что клиент ливнул и требовать нового подключения
у кого требовать? у клиента? он же ливнул : )
сам клиент обычно не знает, что у него изменился адрес или порт
даже если выполнить что-то вроде netsh interface ip set address прямо на клиенте
Sh.Tac.
> у кого требовать? у клиента? он же ливнул : )
> сам клиент обычно не знает, что у него изменился адрес или порт
Ну разумеется, секрет в том, чтобы идентифицировать клиентов не по паре адрес/порт, а по специально сгенерированному имени, которым клиент будет подписывать каждое сообщение.
Или вообще не устанавливать соединений, а работать в режиме запрос-ответ.
Что за программа у ОПа?
Тема в архиве.