LINUX.ORG.RU

Ответ на: комментарий от XMs

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

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

продолжай делать за компилятор его работу.

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

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

И как тут быть без информации о порядке байт на хост-системе?

Во-первых, под линуксами есть endian.h, в котором уже есть функции типа htobe32/htole32/be32toh/le32toh для 16, 32 и 64. Имена не совпадают с OpenBSD, так что это своего рода привязка к GNU/Linux и FreeBSD.

А при чём тут компилятор?

А во-вторых, никто не мешает сделать свои функции преобразования, работающие по байтам. Компилятор тут притом, что он достаточно умный, чтобы работу с байтами преобразовать в одну-две инструкции.


Дизассемблирование раздела .text:

0000000000000000 <be64toh>:
  unsigned char c[8];
  memcpy(c, &s, 8);
  uint64_t ret = ((uint64_t)c[7] << (8 * 0)) | ((uint64_t)c[6] << (8 * 1)) |
                 ((uint64_t)c[5] << (8 * 2)) | ((uint64_t)c[4] << (8 * 3)) |
                 ((uint64_t)c[3] << (8 * 4)) | ((uint64_t)c[2] << (8 * 5)) |
                 ((uint64_t)c[1] << (8 * 6)) | ((uint64_t)c[0] << (8 * 7));
   0:	48 0f cf             	bswap  %rdi
  return ret;
   3:	48 89 f8             	mov    %rdi,%rax
   6:	c3                   	retq   
   7:	66 0f 1f 84 00 00 00 	nopw   0x0(%rax,%rax,1)
   e:	00 00 

0000000000000010 <le64toh>:
  memcpy(c, &s, 8);
  uint64_t ret = ((uint64_t)c[0] << (8 * 0)) | ((uint64_t)c[1] << (8 * 1)) |
                 ((uint64_t)c[2] << (8 * 2)) | ((uint64_t)c[3] << (8 * 3)) |
                 ((uint64_t)c[4] << (8 * 4)) | ((uint64_t)c[5] << (8 * 5)) |
                 ((uint64_t)c[6] << (8 * 6)) | ((uint64_t)c[7] << (8 * 7));
  return ret;
  10:	48 89 f8             	mov    %rdi,%rax
  13:	c3                   	retq   

i-rinat ★★★★★
()
Ответ на: комментарий от beastie

во-первых, это не стандарт и это плюсы. не всегда они есть в проекте, ну и скорость обработки будет сильно страдать. во-вторых, такие трюки можно использовать только когда ты сам кодируешь последовательности. а в сетевых протоколах или в каком-нибудь обмене с железом, например, 3 байта встречаются довольно часто и никаких подобных приблуд там не используется. просто описание протокола с упакованными структурами. и там может быть сколько угодно байтов.

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

это стандартный приём. и там всё равно надо использовать те самые макросы архитектуры и ifdef'ы, о которых я писала выше. и для разных длин будет куча таких конверторов, кроме стандартных ntoh(s/l).

Iron_Bug ★★★★★
()
Ответ на: комментарий от i-rinat

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

Непонятно, о чем спор. Для любого специфичного порядка байт нужно иметь набор функций:

host_to_MyOrder()
MyOrder_to_host()

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

(n >> 0) & 0xFF
(n >> 8) & 0xFF
(n >> 16) & 0xFF
...

Порядок байт на хосте знать не надо! Нужен знать только специфицированный порядок требуемого формата (TIFF, сеть и т.п.)

Deleted
()
Последнее исправление: Deleted (всего исправлений: 2)
Ответ на: комментарий от Iron_Bug

при чём тут понты?

А понты тут при том, что вот это:

я много лет работаю с железом и сетями. если для тебя распаковка сетевых протоколов - это понты, то для меня это обычная рутина, которой приходится заниматься постоянно. и это должно работать быстро, до кучи. в некоторых случаях можно дойти до ассемблерных вставок, если структуры слишком неудобны для обработки, а скорость важна. и ваш позикс, напомню вам, далеко не везде, где есть С. вы так уверены в ваших библиотеках, а их может просто не быть на какой-нибудь железяке.

— понты.

Я уже выше привёл пример того, как компилятор обрабатывает «медленный» код. Результат — одна-две инструкции.

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

Порядок байт на хосте знать не надо!

и какой байт у тебя «младший» в этом случае? на мотороле, внезапно, твой код не работает.

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

во-первых, это не стандарт и это плюсы

:-? вообще-то я о 'variable length encoding' AKA base128, который со староглинянных времён использутеся в том же ASN.1, ProtoBuf и т.д.

ref: 1, 2

beastie ★★★★★
()
Ответ на: комментарий от i-rinat

так всё то же самое: ifdef'ы и работа с байтами. а насчёт «умных компиляторов» под мелкоконтроллеры - я бы не стала так надеяться. ибо в них иногда ещё и ошибки попадались. в каждом конкретном случае надо проверять.

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

и какой байт у тебя «младший» в этом случае? на мотороле, внезапно, твой код не работает.

Вот она — профессиональная деформация. Начисто отбивает возможность думать о числе как о числе без привязки к способу записи.

Есть переменная n. Она представляет собой число. Ты можешь сделать n = n + 1, не заботясь о том, как именно биты расположены в процессоре. Endianness появлятся только при записи в память. Так что младший байт это всегда n & 0xffu, вне зависимости от того, на каком процессоре ты выполняешься и в каком он режиме.

i-rinat ★★★★★
()
Ответ на: комментарий от Iron_Bug

(для 4-х-байтного беззнакового целого n):

/* младший байт */
int x1 = (n) && 0xFF;
/* старший байт */
int x2 = (n >> 24) && 0xFF;

на моторолле этот код не будет работать?

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

ifdef'ы и работа с байтами. а насчёт «умных компиляторов» под мелкоконтроллеры

Во-первых, нафига ifdef'ы в коде под микроконтроллер? Это по определению код, жёстко завязанный под железо.

А во-вторых, нафига ты в тему про переносимость с микроконтроллерами лезешь? С этим своими ассемблерными вставками и всем прочим.

i-rinat ★★★★★
()
Ответ на: комментарий от beastie

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

Iron_Bug ★★★★★
()
Ответ на: комментарий от i-rinat

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

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

на моторолле этот код не будет работать?

я тебе задала вопрос: куда ты будешь пихать свой «младший» байт, не зная порядка байт на машине? вот подумай головой сначала, а потом пиши уже.

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

вот подумай головой сначала, а потом пиши уже.

вот подумай головой сначала, а потом пиши уже.

i-rinat ★★★★★
()
Ответ на: комментарий от Iron_Bug

Ой всё.

куда ты будешь пихать свой «младший» байт

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

u8 my_coded_data[123];
my_coded_data[SPEC_FIELD_LITTLE] = (n >> 0) && 0xFF; /* "младший" */
Deleted
()
Последнее исправление: Deleted (всего исправлений: 2)
Ответ на: комментарий от Iron_Bug
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
  if (argc < 2)
    return 0;
  unsigned int a = atoi(argv[1]);
  printf("%02x %02x %02x %02x\n", (a >> 24) & 0xffu, (a >> 16) & 0xffu,
         (a >> 8) & 0xffu, a & 0xffu);
  return 0;
}

Вот код, который печатает число в big-endian. Сначала старший байт, в конце — младший. На моторолле не будет работать, да? Ifdef'ами придётся код обмазать?

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

что ты придираешься? человек не выспался

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

* Нет проверки на размер unsigned int.

* Стандартная сигнатура main для микроконтроллеров не всегда подходит.

* Использование стандартной библиотеки увеличивает размер программы. Иногда критично.

* Операции извлечения байтов не вынесены в отдельные функции - чтрадает читабельность.

=> Этот пример не универсальный - подходит только для некоторых платформ.

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

Компилятор тут притом, что он достаточно умный, чтобы работу с байтами преобразовать в одну-две инструкции.

Спасибо за информацию. Для меня действительно стало новостью, что такие спагетти компилятор догадался упаковать в одну команду bswap.

Но это не избавляет программиста от необходимости писать спагетти в исходном тексте. А #ifdef'ы использовать для того, чтоб узнать, нужны они или нет, если по каким-то причинам функции из <endian.h> и <arpa/inet.h> нам не подходят.

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

Нет проверки на размер unsigned int

А ничего, что в коде вообще atoi, а не strtol используется? Само использование atoi уже показывает, что на такие ошибки плевать. Пример показывает разбор целого на байты. Смотри туда. Ты бы ещё докопался до примеров в стандарте. А что? Они вообще не компилируются, так как не являются полными программами.

Стандартная сигнатура main для микроконтроллеров не всегда подходит.

Тогда это уже не Си.

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

В Си всегда есть стандартная библиотека, на то она и стандартная. Дополнительные требования вроде работы без стандартной библиотеки — это уже отдельная песня. Возможно, для тебя это обычное дело. Но нужно осознавать, что это не стандартный Си.

Операции извлечения байтов не вынесены в отдельные функции - чтрадает читабельность.

А это уже смешно. Я могу понять, зачем это делать в программах побольше. Хотя бы для изоляции кода, подверженного ошибкам. Но говорить про читаемость в примере из десяти строк это перегиб. Предлагаешь внести побольше сущностей, чтобы превратить простой пример в нечитаемое месиво?

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

можно даже ещё короче.

uint32_t _l32b(uint32_t i){
    return ((i&255) << 24)       | (((i>>8) & 255) << 16)|
           (((i>>16) & 255) << 8)| (((i>>24) & 255));
}

uint64_t _l64b(uint64_t i){
    return ((i&255) << 56)       | (((i>>8) & 255) << 48)|
           (((i>>16)&255) << 40) | (((i>>24) & 255) << 32)|
           (((i>>32)&255) << 24) | (((i>>40) & 255) << 16)|
           (((i>>48)&255) << 8)  | ((i>>56) & 255);
}
Получается
0000000000400700 <_l32b>:
  400700:	89 f8                	mov    %edi,%eax
  400702:	0f c8                	bswap  %eax
  400704:	c3                   	retq   

0000000000400705 <_l64b>:
  400705:	48 89 f8             	mov    %rdi,%rax
  400708:	48 0f c8             	bswap  %rax
  40070b:	c3                   	retq   
  40070c:	0f 1f 40 00          	nopl   0x0(%rax)

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

Но это не избавляет программиста от необходимости писать спагетти в исходном тексте.

Да. Но тут ещё вот какое дело. Почему-то люди ошибаются во время оперирования с данными различного формата. Забыли, что где-то данные в структуре лежат в bigendian, и вот уже какой-то странный баг.

Если придерживаться правила, по которому все данные в любой структуре в памяти всегда находятся в родном порядке байт, число таких ошибок можно сократить. Все преобразования оставить для кодов сериализации/десериализации. Тогда просто смотря на код можно будет подобные ошибки ловить. Нет преобразования — ошибка.

Конечно, всегда будут оставаться ситуации, в которых нет смысла эти преобразования делать. В общем, там, где такты экономят. Но не зря для Linux сделали sparse. Видать, ошибки be/le — очень частые.

i-rinat ★★★★★
()
Ответ на: комментарий от beastie

Знать то что есть Little-Endian и Big-Endian полезно. И я ничего плохого не вижу, чтобы с помощью функции или макроса узнать какая у меня архитектура.

А то половина сообщений: зачем тебе знать тип архитектуры, тебе это не нужно, используй те функции и т.д.

neon1ks ★★
()
Последнее исправление: neon1ks (всего исправлений: 1)
Ответ на: комментарий от neon1ks

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

А как же документация?

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

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

Никто, конечно, не осудит твои исследования в этом. Это нормально.

А плохо может быть то, что ты ненароком «продырявишь» абстракцию в реальном проекте. И это приведет к лишней работе по рефакторингу в будущем. Но, возможно, последствия серьезней, когда эта «дырка» будет считаться единственно правильным вариантом, что отразится на дальнейшей архитектуре.

Поэтому, я (и, вероятно, другие), заостряю внимание на том, что в подавляющем большинстве случаев код вида #if __BYTE_ORDER__ - плох, т.к., вероятно, применен без обоснований.

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

При размере int 64 бита, половина числа не выведется. При размере 16 - выведутся несуществующие 0х00. Возражение не возникло бы при использовании uint32_t.

В МК обычно нет консоли как таковой. int main ( void ) - описан в стандарте,

Про использование на МК мотороллы ты написал первым.

Это для того, чтобы люди не копировали из интернета тяп-ляп написанный код в рабочие проекты. От 4 строчек с взятием байтов хуже не будет.

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

на МК мотороллы

Моторолла это не только МК.

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

Все преобразования оставить для кодов сериализации/десериализации.

С этим полностью согласен.

aureliano15 ★★
()
Ответ на: комментарий от i-rinat

там, где такты экономят

Сэкономивший такт дебажит дважды. (с) Народная мудрость

beastie ★★★★★
()
Ответ на: комментарий от i-rinat

Да, в принципе тоже код наглядный, хотя вариант с массивом наглядней, да. Это моя стойкая идиосинкразия на memcpy/memmove во что-то - как цепляюсь глазом, так ищу потенциальную ошибки. Поэтому стараюсь минимизировать использование где возможно, если это не сильно бьёт по читаемости, конечно.
Хотя, в данном случае, понятно и видно, что функцию оптимизация выбросит.

SkyMaverick ★★★★★
()

Уважаемые beastie, i-rinat, PtiCa и Iron bug, хочу внести свой вброс. Сдвиги это хорошо, когда они нужны, например на машине с LE при пересылке в сетевом формате, но не на машине с BE, там где ничего для этого делать ненадо, так как сдвиги это лишние операции на машине с BE. Почему бы мне не создать макрос котгрый на машине с LE разворачивался бы в сдвиги, а на машине с BE разворачивался бы в простое копирование? Для этого и нужно знать порядок байт на машине. Может ваш компилятор и оптимизируети сдвиги на BE машине сам, но сам язык, на сколько я понимаю, никак не обязывает писателей компиляторов писать оптимизацию.

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

но сам язык, на сколько я понимаю, никак не обязывает писателей компиляторов писать оптимизацию

Сам язык никак не запрещает писателей компиляторов не вставлять nop'ы или пустые циклы в сгенерированный код. Кстати, GCC таки вставляет в код nop'ы.

Почему бы мне не создать макрос котгрый на машине с LE разворачивался бы в сдвиги, а на машине с BE разворачивался бы в простое копирование?

Советы не делать так были только из-за того, что такие манипуляции сильно подвержены ошибкам. Статистически, ты всё равно накосячишь где-нибудь и когда-нибудь. Разные подходы просто меняют твои шансы допустить ошибку.

А так, если тебе хочется разложить по коду грабли, чтобы их потом собирать, никто не сможет тебе помешать. Удачи.

i-rinat ★★★★★
()
Ответ на: комментарий от normann

Внутри функций а-ля h_to_be64(), h_to_le64() я бы применил #if __BIG_ENDIAN__ ... .

Причем код

uint64_t h_to_le64(const uint64_t * n)
{
    uint64_t rval;
    uint8_t * const out = (uint8_t *)&rval;
    
    for(int i = 0; i < sizeof(rval); --i) {
        out[i] = (*n >> (i * 8));
    }
    return rval; 
}

по сути эквивалентен

uint64_t h_to_le64(const uint64_t * n)
{
#if __LITTLE_ENDIAN__
    return *n;
#else
    uint64_t rval;
    uint8_t * const out = (uint8_t *)&rval;
    
    for(int i = 0; i < sizeof(rval); --i) {
        out[i] = (*n >> (i * 8));
    }
    return rval; 
#endif
}

Условная компиляция только для оптимизации, но семантика не меняется. Все, мы имеем удобные, довольно прозрачные и при этом оптимальные функции, абстрагирующие нас от хост-архитектуры.

UPD ну, и код для be

uint64_t h_to_be64(const uint64_t * n)
{
#if __BIG_ENDIAN__
    return *n;
#else
    uint64_t rval;
    uint8_t * const out = (uint8_t *)&rval;

    for(int i = 0; i < sizeof(rval); ++i) {
        out[sizeof(rval) - 1 - i] = (*n >> (i * 8));
    }

    return rval; 
#endif
}
Deleted
()
Последнее исправление: Deleted (всего исправлений: 3)
Ответ на: комментарий от normann

Именно так и работает htonl с ntohl.

Соль в том, что за редкими исключениями, манипуляции с endianness в прикладном софте — резкий и явный признак code smell.

beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 2)
Ответ на: комментарий от i-rinat

А его в живой природе последние лет десять видели?

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

ты хоть раз что-нибудь писал для сети? напиши - тогда расскажешь всем, как он «неважен».

ты хоть ветку эту целиком прочитай прежде чем такое писать //_-)

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

я тебе задала вопрос: куда ты будешь пихать свой «младший» байт, не зная порядка байт на машине? вот подумай головой сначала, а потом пиши уже.

Вон из профессии! С ужасом себе представляю, что ты там для сети пишешь.

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

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

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

Классический инженер с тридцатилетним стажем никогда не ошибается. Это мир неправильный и математика кривая.

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

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

у инженера есть масса опыта по отладке и тестированию

Проблема только в том, что эта масса зачастую отбивает способность мыслить. Мысли заменяются шаблонами.

Я про тебя сейчас :-D. Ты несёшь чушь, причём это даже на йоту не чувствуешь.

Вообще выражение «инженер с тридцатилетним стажем» это уже классика. Пора и знать.

i-rinat ★★★★★
()
Ответ на: комментарий от Iron_Bug

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

Если ты не знаешь, как платформонезависимо упаковать байтики в int и, более того, до сих пор не прочитала об этом в _ЭТОЙ_ВЕТКЕ_, то это много говорит о твоей квалификации.

Waterlaz ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.