Упаковка битовых полей еще плотнее
У меня проблема с битовыми полями в производных классах.
С помощью компилятора g++ вы можете назначить __attribute__((packed))
к классу, и он будет упаковывать битовые поля. Так
class A
{
public:
int one:10;
int two:10;
int three:10;
} __attribute__ ((__packed__));
занимает всего 4 байта. Все идет нормально.
Однако, если вы наследуете класс, как это
class B
{
public:
int one:10;
int two:10;
} __attribute__ ((__packed__));
class C : public B
{
public:
int three:10;
} __attribute__ ((__packed__));
Я ожидаю, что класс C, который имеет то же содержимое, что и класс A, описанный выше, также будет иметь такой же макет, то есть занимать 4 байта. Однако C оказывается 5 байтов.
Итак, мой вопрос, я делаю что-то не так, и если да, то что? Или это проблема с компилятором? Недосмотр, реальная ошибка?
Я пробовал гуглить, но ничего не придумал, кроме разницы между Linux и Windows (где компилятор пытается эмулировать MSVC), которая мне не интересна. Это только на Linux.
2 ответа
Я полагаю, что проблема с B, который не может легко быть 2,5 байта. Должно быть не менее 3 байтов.
Теоретически, производному классу может быть разрешено повторное использование отступов из базового класса, но я никогда не видел, чтобы это произошло.
Представьте на секунду, что то, о чем вы просите, возможно. Каковы будут возможные побочные эффекты или проблемы этого? Давайте посмотрим на конкретный пример, который у вас есть. Также предположим, что 32-битная архитектура с 1-байтовым выравниванием памяти.
20 последовательных битов class A
что вы можете обратиться через членов класса one
а также two
, Это очень удобное для вас обращение, человек. Но что делает компилятор, чтобы это произошло? Он использует маски и биты, чтобы расположить эти биты в правильных местах.
Пока все хорошо, кажется простым и достаточно безопасным.
Добавляем еще 10 бит. Допустим, был какой-то удивительно умный компилятор, который позволяет втиснуть эти лишние 10 бит в уже используемое 32-битное слово (они прекрасно вписываются, не так ли?).
Здесь приходит беда:
A* derived = new B; // upcast to base class
derived->one = 1;
derived->two = 2;
// what is the value of derived->three in this context?
// Especially taking into account that a compiler is free to do all sorts
// of optimizations when generating code for class A
Из-за вышеизложенного класс должен использовать разные и отдельно адресуемые области памяти для членов class A
и члены class B
заставляя эти 10 битов "пролиться" в следующую адресуемую ячейку памяти - следующий байт.
Еще больше проблем возникает, когда вы рассматриваете множественное наследование - каков единственный верный способ размещения битов в производном классе?