LINUX.ORG.RU

Битовые поля

 ,


0

1

Привет. Есть структура вида:

struct {
   struct {
      unsigned a : 1;
      unsigned b : 1;
      ...
   } p;
} MyStruct;
все поля по одному биту. Есть ли быстрый способ узнать, возведён ли хоть один бит без if'a по каждому полю?

Покумекал тут. Вносите царя, короче:

   bool pChanged = false;
   int numberOfBytes = sizeof(((MyStruct*)0 )->p) / sizeof(int);
   int* pBitField = reinterpret_cast<int*>(&myStruct.p);
   while (numberOfBytes > 0) {
      if (*pBitField) {
         pChanged = true;
         break;
      }
      ++pBitField;
      --numberOfBytes;
   }

★★★★★

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

А сколько байт в структуре? Сделать reinterpret_cast к нужному целочисленному типу и проверить значение на 0.

yoghurt ★★★★★
()
union {
   struct p;
   int i;
} u;

u.p = 1;

if (u.i) {
    puts("something is there");
}
beastie ★★★★★
()

Можно завернуть структуру в union с интом и прверить, равен ли этот инт нулю:

union {
  unsigned allBits;
  struct {
    unsigned a:1;
    unsigned b:1;
  }
} x;

if (x.allBits != 0) {
  // какой-то бит не 0 
}
Если полей больше 32, можно заменить int на long. И желательно не забыть обнулить allBits при инициализации структуры :)

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

А сколько байт в структуре?

Пока 5.

UVV ★★★★★
() автор топика

Или макрос набросать

struct P {
    unsigned a:1;
    unsigned b:1;
    unsigned c:1;
    unsigned d:1;
    unsigned e:1;
};

#define IS_BIT_SET(data) \
((1 & data.a) || (1 & data.b) || (1 & data.c) || (1 & data.d) || (1 & data.e))

Shadow1251
()

Кстати, а все основные компиляторы (gcc, clang, vs) на основных платформах (amd64, arm64) гарантируют, что битовое поле будет занимать ровно столько места, сколько указано, не смотря ни на что? Стандарт ведь не гарантирует ничего.

mix_mix ★★★★★
()

если int* поменять на uint8_t*, то пойдёт..

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

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

в любом случае старайся делать проверку на !=0. все остальное априори будет медленнее

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

Байтами лучше? Почему?

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

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

MKuznetsov ★★★★★
()

Вопрос чисто теоретический? Ну в смысле как именно при таком виде хранения данных не пользовать If, или нужна наибольшая производительность при сохранении той же структуры данных (опять же чисто теоретический интерес)?

А можно ли вообще поменять структуру данных? Или она настолько вшита в систему, что нет никакой возможности поменять структуру (что очень моловероятно)?

Или таки нужна наиболее быстрая реализация (в смысле исполнения кода) с наиболее оптимальным хранением?

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

байт - минимально адресуемая сущность

Схера ли? Структура выравнивается по размер int'a же, 4 байта.

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

Структура выравнивается по размер int'a же, 4 байта.

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

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

кстати из-за выравниваний, укажите вашу мега-структуру как packed(1) или аккуратно инициализуйте её через memset - иначе можно поймать слабоуловимую ошибку когда в гепах присутсвует мусор

MKuznetsov ★★★★★
()

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

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

Конструктивно, пожалуйста. Пока что ты пёрнул в лужу. Какую роль в моём коде сыграет порядок, и как вдруг неправильно расчитается размер?

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

тебе говорят, что union может работать по разному на разных платформах. Потому лучше его не юзать. Хотя если ограничиваться x86, то это работает.

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

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

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

тебе говорят, что union может работать по разному на разных платформах

Ткни, пожалуйста, где у меня union. Просто не помню, чтобы набирал это слово.

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

Ткни, пожалуйста, где у меня union.

у тебя конкретно нет. Но в теме есть: Битовые поля (комментарий)

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

А какие ещё есть решения этой проблемы? Если хочешь портабельности, то сделай inline функцию с проверкой всех бит. Умный компилятор сам должен догадаться, и оптимизировать как с union, но с учётом порядка бит и размера.

inline bool iszero(const struct& s)
{
  return !(s.b0 || s.b1 || s.b2)
}
emulek
()
Ответ на: комментарий от emulek

Если хочешь портабельности, то сделай inline функцию с проверкой всех бит.

Я думал, что с указателем эффективнее решение будет. Ну понятно, что inline компилер может соптимизировать, но 40-50 полей перечислять в функции как-то неправильно что ли.

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

Другая причина, почему мне нравится моя версия, так это то, что при расширении структуры мне ну нужно будет ни о чём заботиться. А функцию нужно не забыть будет обновить.

UVV ★★★★★
() автор топика

кстати, чтобы вам в муках не рожать велосипед со всякими кастами и прочими юнионами - можно просто тупо сравнить со статической абсолютно пустой структурой :-) хоть через memcmp в перегруженном ==

static const MyStruct zeroFilled={0};

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

А это мысль, спасибо! Я думал в эту сторону, но до статики не додумал.

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

Я думал, что с указателем эффективнее решение будет.

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

но 40-50 полей

кстати в int это не влезет на amd64 & IA-32. Сейчас int в 32 бита.

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

что при расширении структуры мне ну нужно будет ни о чём заботиться.

если-бы. Когда девятый и 33й бит будешь добавлять, есть риск что всё сломается.

Кстати функцию в C++ можно запихать в саму структуру.

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

ты уверен, что если в структуре используются 30 битов, то неиспользуемые будут обязательно сброшены? Ну и оверхед на memcmp когда нужно одно целое с нулём сравнить. Memcmp сравнивает байты как ASCII символы, и даёт ответ «больше» или «меньше». Для этого приходится разворачивать int задом наперёд если не хочешь сравнивать по одному байту. А тут просто с нулём надо сравнить.

emulek
()

Есть ли быстрый способ узнать, возведён ли хоть один бит без if'a по каждому полю?

Что значит «быстрый»? Меньше кода писать или быстрее исполнять?

На код

struct a {
  unsigned f1:1;
  unsigned f2:1;
  unsigned f3:1;
  unsigned f4:1;
  unsigned f5:1;
  unsigned f6:1;
  unsigned f7:1;
  unsigned f8:1;
  unsigned f9:1;
};

int if_any_set(struct a p)
{
    return p.f1 || p.f3 || p.f5 || p.f7 || p.f9;
}

gcc -O1 генерирует

0000000000000000 <if_any_set>:
   0:	31 c0                	xor    %eax,%eax
   2:	81 e7 55 01 00 00    	and    $0x155,%edi
   8:	0f 95 c0             	setne  %al
   b:	c3                   	retq   

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

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

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

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

А где f2, f4 etc?

Специально их опустил, чтобы было видно, что и в этом случае GCC соображает, что делать.

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

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

AFAIK стандарт этого не гарантирует, а компилятор никогда не делает лишнюю работу. Т.ч. мусор наверняка будет.

А где f2, f4 etc?

and $0x155,%edi фильтрует нужные пять битов. За одно выставляет/сбрасывает флаг Z.

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

ты уверен, что если в структуре используются 30 битов, то неиспользуемые будут обязательно сброшены?

читай-смотри уже весь тред. Парой постов до того я об этом писал

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

ты уверен, что если в структуре используются 30 битов, то неиспользуемые будут обязательно сброшены?

читай-смотри уже весь тред. Парой постов до того я об этом писал

ты писал про packed(1), которая закроет дыры в байт. А я писал про дыры размеров меньше байта.

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

он писал, что если memset сделать, то оно сбросит все padding биты в 0. Я на SO тоже видел тред об этом.

UVV ★★★★★
() автор топика

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

struct S {
	unsigned a:1;
	unsigned b:1;
	unsigned c:1;
};

_Bool test_S(const struct S *s)
{
	return s->a || s->b || s->c;
}
При этом test_S компилируется в
_Bool test_S(const struct S *s)
{
    return s->a || s->b || s->c;
  400580:   f6 07 07                testb  $0x7,(%rdi)
  400583:   0f 95 c0                setne  %al
  400586:   c3                      retq
}

Определенно было бы разумно вынести эту проверку в отдельную функцию (можно static inline, __attribute__((always_inline)) или макрос), чтобы не искать каскады if'ов по всему коду при добавлении новых полей.

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

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

Shadow1251
()

Нормально, ходишь от байта к байту:

std::size_t bytes = sizeof(p);
unsigned char* step = reinterpret_cast<unsigned char*>(&p);
while (bytes--) {
    if ((*step++)) {
        break;	    
    }
}

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

Да, так и сделал. Только размер вычисляю через нулевой указатель, поскольку структура неименная. Если интересно, могу ссылку на SO дать, где об этом рассказывают

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

Да, так и сделал.

ОК. Потом не плачь, тебя предупреждали, что reinterpret_cast опасен. К тому же выше был пруф того, что компилятор сам в состоянии скастовать поле к целому, причём безопасно и также быстро. Но ты всё равно делаешь через жопу.

Почему вы все такие упрямые?

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