Как создать 24-битное целое число без знака в C - PullRequest
1 голос
/ 19 сентября 2019

Я работаю над встроенным приложением, где ОЗУ очень мало.Для этого мне нужно создать 24-битный беззнаковый целочисленный тип данных.Я делаю это, используя структуру:

typedef struct
{
    uint32_t v : 24;
} uint24_t;

Однако, когда я запрашиваю размер переменной этого типа, он возвращает «4», то есть:

    uint24_t x;
    x.v = 0;
    printf("Size = %u", sizeof(x));

Есть ликак заставить эту переменную иметь 3 байта?

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

typedef struct
{
    uint8_t blah[3];
} mytype;

Ив этом случае размер выходит на 3.

Ответы [ 4 ]

2 голосов
/ 19 сентября 2019

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

#pragma pack(push, 1)
typedef struct { uint8_t byt[3]; } UInt24;
#pragma pack(pop)

Вы возможно должныпредоставьте эти директивы компилятора (например, строки #pragma выше), чтобы гарантировать отсутствие заполнения, но это будет , вероятно, по умолчанию для структуры с только восьмибитными полями (a) .

Тогда вам, вероятно, придется упаковывать / распаковывать реальные значения в структуру и из структуры, что-то вроде:

// Inline suggestion used to (hopefully) reduce overhead.

inline uint32_t unpack(UInt24 x) {
    uint32_t retVal = x.byt[0];
    retval = retVal << 8 | x.byt[1];
    retval = retVal << 8 | x.byt[2];
    return retVal;
}

inline UInt24 pack(uint32_t x) {
    UInt24 retVal;
    retVal.byt[0] = (x >> 16) & 0xff;
    retVal.byt[1] = (x >> 8) & 0xff;
    retVal.byt[2] = x & 0xff;
    return retVal
}

Обратите внимание, что это дает значения с прямым порядком байтов независимо от вашей фактической архитектуры.Это не имеет значения, если вы упаковываете и распаковываете сами, но это может стать проблемой, если вы хотите использовать блоки памяти где-либо еще в определенной компоновке (в этом случаеВы можете просто изменить код упаковки / распаковки для использования нужного формата).

Этот метод добавляет небольшой код в вашу систему (и, возможно, минимальное снижение производительности), поэтому вам придется решить, стоит ли этосохранение в используемом пространстве данных.


(a) Например, gcc 7.3 и clang 6.0 показывают 3 6 для следующей программы, показывая, что заполнение отсутствуетв структуре или в следующей структуре:

#include <stdio.h>
#include <stdint.h>

typedef struct { uint8_t byt[3]; } UInt24;
int main() {
    UInt24 x, y[2];
    printf("%zd %zd\n", sizeof(x), sizeof(y));
    return 0;
}

Однако, - это всего лишь пример, поэтому вы можете рассмотреть в интересах переносимого кода использование чего-то вроде #pragma pack(1),или вставляя код, чтобы поймать среды, где это может быть не так.

1 голос
/ 19 сентября 2019

Изначально я думал, что это потому, что вынуждает типы данных выравниваться по словам

Разные типы данных могут иметь различное выравнивание.См., Например, Объекты и выравнивание doc.

. Вы можете использовать alignof для проверки, но для char или uint8_t вполне нормально иметь 1 байт (т.е.фактически нет) выравнивания, но для uint32_t нужно иметь выравнивание в 4 байта.Я не знаю, явно ли описано выравнивание битовых полей, но унаследовать его от типа хранилища кажется достаточно разумным.

Примечание.Причиной наличия требований к выравниванию обычно является то, что он лучше работает с базовым оборудованием.Если вы делаете , используете #pragma pack или __attribute__((packed)) или что-то еще, вы можете получить удар по производительности в качестве компилятора - или аппаратного обеспечения памяти - для бесшумной обработки не выровненных обращений.

Просто явно храните3-байтовый массив, вероятно, лучше, IMO.

1 голос
/ 19 сентября 2019

Комментарий Жуана Баптиста на этом сайте говорит, что вы можете использовать #pragma pack.Другой вариант - использовать __attribute__((packed)):

#ifndef __GNUC__
# define __attribute__(x)
#endif
struct uint24_t { unsigned long v:24; };
typedef struct uint24_t __attribute__((packed)) uint24_t;

. Это должно работать на GCC и Clang.

Обратите внимание, однако, что это, вероятно, испортит выравнивание, если ваш процессор не поддерживает доступ без выравнивания.

0 голосов
/ 19 сентября 2019

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

Если ваш ЦП явно не получил 24-битные арифметические инструкции - что не кажется очень вероятным, если это не какой-то странный DSP - тогда пользовательскийТип данных не даст ничего, кроме дополнительного раздувания стека.

Скорее всего, вам придется использовать uint32_t для всей арифметики.Это означает, что ваш 24-битный тип может не достичь многого, когда речь заходит об экономии оперативной памяти.Если вы изобрели какой-нибудь пользовательский ADT с доступом к сеттеру / получателю (сериализация / десериализация), вы, вероятно, просто тратите ОЗУ, поскольку вы получаете более высокий пиковый уровень использования стека, если функции не могут быть встроены.

Чтобы реально сэкономить ОЗУ, вам лучше пересмотреть дизайн вашей программы.


При этом вы можете создать собственный тип на основе массива:

typedef unsigned char u24_t[3];

Всякий раз, когда вывам нужно получить доступ к данным, вы memcpy получите их в / из 32-битного типа и затем выполните всю арифметику с 32-битным кодом:

u24_t    u24;
uint32_t u32;
...
memcpy(&u32, u24, sizeof(u24));
...
memcpy(&u24, &u32, sizeof(u24));

Но учтите, что это подразумевает немного порядка байтов, так как мы работаем только сбиты от 0 до 2. В случае системы с прямым порядком байтов вам нужно будет сделать memcpy((uint8_t*)&u32+1, ..., чтобы сбросить байт MS.

...