Доступ к битам в char через битовое поле - PullRequest
1 голос
/ 05 апреля 2019

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

#include <stdbool.h>
#include <stdio.h>

typedef struct {
    bool _1 : 1, _2 : 1, _3 : 1, _4 : 1, _5 : 1, _6 : 1, _7 : 1, _8 : 1;
} bits;

int main() {
    char c = 0;
    bits *b = (bits *)&c;
    b->_3 = 1;
    printf("%s\n", c & 0x4 ? "true" : "false");
}

Компилируется без ошибок или предупреждений с gcc -Wall -Wextra -Wpedantic test.c. При запуске получившегося исполняемого файла с valgrind он не сообщает об ошибках памяти. Сборка, сгенерированная для назначения b->_3 = 1;, является or eax, 4, что является звуком.

Вопросы

  • Это определенное поведение в C?
  • Это определенное поведение в C ++?

N.B .: Я знаю, что это может вызвать проблемы со смешанным порядком байтов, но у меня только маленький порядковый номер.

1 Ответ

4 голосов
/ 05 апреля 2019

Это определенное поведение в C?
Это определенное поведение в C ++?

TL; DR: нет, это не так.

Логическое битовое поле четко определено, поскольку: bool - это нормально используемый тип для битовых полей, поэтомувы гарантированно получите двоичный объект из 8 логических значений, выделенных где-то в памяти.Если вы получите логический _1, вы получите то же значение, что и в прошлый раз, когда обращались к этой переменной.

То, что не определено, это битовый порядок.Компилятор может вставлять биты заполнения или байты заполнения по своему усмотрению.Все это определяется реализацией и не является переносимым.Таким образом, вы не можете точно знать, где находится _1 в памяти или это MSB или LSB.Ничто из этого не является четко определенным.

Однако, bits *b = (bits *)&c; доступ к char через указатель структуры является строгим нарушением псевдонимов и может также вызвать проблемы с выравниванием.Это неопределенное поведение в C и C ++.Вам, по крайней мере, нужно показать эту структуру в union с char, чтобы избежать строгого алиасинга, но вы все равно можете получить икоты выравнивания (и C ++ хмурится при пробивании типов через объединения).

(Ипереход от логического типа к символьному типу также может дать некоторые реальные сумасшедшие результаты, см. _Bool type и строгое псевдонимы )


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

c |= 1u << n;     // set bit n
c &= ~(1u << n);  // clear bit n

Это переносимый тип, не зависящий от типа и порядка байтов.

(Хотя, чтобы избежать изменения подписи из-за неявных целочисленных повышений, рекомендуется всегда приводить результат ~ обратно к предполагаемому типу: c &= (uint8_t) ~(1u << n);).

Примечаниечто тип char совершенно не подходит для побитовой арифметики, поскольку он может быть или не быть подписанным.Вместо этого вы должны использовать unsigned char или предпочтительно uint8_t.

...