Объединение структур только с битовыми полями, размер функции удваивает байты, C - PullRequest
3 голосов
/ 05 марта 2019

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

#include <stdio.h>
#include <stdlib.h>

union instructionSet {
    struct Brane{
        unsigned int opcode: 4;
        unsigned int address: 12;
    } brane;
    struct Cmp{
        unsigned int opcode: 4;
        unsigned int blank: 1;
        unsigned int rsvd: 3;
        unsigned char letter: 8;
    } cmp;
    struct {
        unsigned int rsvd: 16;
    } reserved;
};

int main() {

    union instructionSet IR;// = (union instructionSet*)calloc(1, 2);

    printf("size of union %ld\n", sizeof(union instructionSet));
    printf("size of reserved %ld\n", sizeof(IR.reserved));
    printf("size of brane %ld\n", sizeof(IR.brane));
    printf("size of brane %ld\n", sizeof(IR.cmp));


    return 0;
}

Все вызовы sizeof возвращают 4, однако, насколько мне известно, они должны возвращать 2.

Ответы [ 4 ]

2 голосов
/ 05 марта 2019

C 2018 6.7.2.1 11 позволяет реализации C выбирать размер контейнера, который используется для битовых полей:

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

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

2 голосов
/ 05 марта 2019

Здесь есть пара проблем, во-первых, ваш битрейн Brane использует unsigned int, который составляет 4 байта.

Даже если вы просто используете половину битов, вы все равно используете целую 32-битную ширину unsigned int.

Во-вторых, ваши битовые поля Cmp используют два разных типа полей, поэтому вы используете 8-битный из 32-битного unsigned int для ваших первых 3 полей, а затем вы используете беззнаковый символ для его полного 8-битного. Из-за правил выравнивания данных эта структура должна быть не менее 6 байтов, но потенциально больше.

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

Примерно так будет полностью оптимизирован ваш союз:

union instructionSet {
    struct Brane{
        unsigned short opcode: 4;
        unsigned short address: 12;
    } brane;
    struct Cmp{
        unsigned short opcode: 4;
        unsigned short blank: 1;
        unsigned short rsvd: 3;
        unsigned short letter: 8;
    } cmp;
    struct {
        unsigned short rsvd: 16;
    } reserved;
};

Это даст вам размер 2 вокруг.

1 голос
/ 05 марта 2019

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

union instructionSet {

    /* any number of padding bits may be inserted here */ 

    /* we don't know if what will follow is MSB or LSB */

    struct Brane{
        unsigned int opcode: 4; 
        unsigned int address: 12;
    } brane;
    struct Cmp{
        unsigned int opcode: 4;
        unsigned int blank: 1;
        unsigned int rsvd: 3;
        /* anything can happen here, "letter" can merge with the previous 
           storage unit or get placed in a new storage unit */
        unsigned char letter: 8; // unsigned char does not need to be supported
    } cmp;
    struct {
        unsigned int rsvd: 16;
    } reserved;

    /* any number of padding bits may be inserted here */ 
};

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

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

Вещи, которые мы не можем знать:

  • Насколько велики битовые поля типа unsigned int.32 бита могут иметь смысл, но не гарантируют.
  • Если для битовых полей разрешено unsigned char.
  • Насколько велики битовые поля типа unsigned char.Может быть любого размера от 8 до 32.
  • Что произойдет, если компилятор выбрал меньший блок памяти, чем ожидаемые 32 бита, и биты не помещаются внутри него.
  • Что произойдет, если битовое поле unsigned int встретится с битовым полем unsigned char.
  • Если в конце объединения или в начале (выравнивание) будет заполнение.
  • Как выровнены отдельные единицы хранения в структурах.
  • Местоположение MSB.

Вещи, которые мы можем знать:

  • Мы создали некоторый двоичный двоичный объект в памяти.
  • Первый байт большого двоичного объекта находится на наименее значимом адресе в памяти.Он может содержать данные или заполнение.

Дополнительные знания можно получить, имея в виду очень специфическую систему и компилятор.


Вместо битовых полей, которые мы можем использовать100% переносимые и детерминированные побитовые операции, которые в любом случае дают один и тот же машинный код.

1 голос
/ 05 марта 2019

Читайте о заполнении структуры памяти / выравнивании памяти. По умолчанию 32-битный процессор читает из памяти 32-битный (4 байта), потому что быстрее. Таким образом, в память char + uint32 будет записано 4 + 4 = 8 байт (1 байт - символ, 3 байт пространства, 4 байт uint32).

Добавьте эти строки в начало и конец вашей программы и получите результат 2.

#pragma pack(1)

#pragma unpack

Это способ сказать компилятору: выровнять память по 1 байту (по умолчанию 4 на 32-битном процессоре).

PS: попробуйте этот пример с другим #pragma pack набором:

struct s1 
{
    char a;
    char b;
    int c;
};

struct s2
{    
    char b;
    int c;
    char a;
};

int main() {
    printf("size of s1 %ld\n", sizeof(struct s1));
    printf("size of s2 %ld\n", sizeof(struct s2));

    return 0;
}
...