如何在 C 中创建 24 位无符号整数

How to create 24 bit unsigned integer in C

我正在开发一个 RAM 非常紧张的嵌入式应用程序。 为此,我需要创建一个 24 位无符号整数数据类型。我正在使用结构来执行此操作:

typedef struct
{
    uint32_t v : 24;
} uint24_t;

然而,当我询问这种类型的变量的大小时,它returns“4”,即:

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

有没有办法强制这个变量有 3 个字节?

最初我认为这是因为它强制数据类型按字对齐,但我可以这样做:

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

在这种情况下,大小为 3。

嗯,你可以尝试确保结构只占用你需要的space,比如:

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

可能必须提供那些编译器指令(如上面的 #pragma 行)以确保没有填充,但这 可能 是只有八位字段的结构的默认值(a).

然后您可能必须 pack/unpack 实际值进出结构,例如:

// 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;
}

请注意,无论您的实际架构如何,这都会为您提供大端值。如果您只是自己打包和解包,这无关紧要,但如果您想在 特定 [=] 中的其他地方使用内存块,则 可能 41=] 布局(在这种情况下,您只需更改 pack/unpack 代码即可使用所需的格式)。

此方法会向您的系统添加一些代码(并且可能会带来最小的性能损失),因此您必须决定是否值得节省 space 使用的数据。


(a)比如gcc 7.3clang 6.0都显示3 6下面的程序,说明没有padding在结构内或结构后:

#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) 之类的东西,或者将代码放入捕获可能不是这种情况的环境。

A comment by João Baptista on this site 表示可以使用 #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。

但是请注意,除非您的处理器支持未对齐访问,否则这可能会搞砸对齐。

Initially I thought it was because it is forcing datatypes to be word aligned

不同的数据类型可以有不同的对齐方式。例如,参见 Objects and alignment 文档。

你可以使用alignof来检查,但是charuint8_t有1字节对齐是完全正常的(即实际上没有),但是uint32_t 进行 4 字节对齐。我不知道是否明确描述了位域的对齐方式,但是从存储类型继承它似乎很合理。

注意。具有对齐要求的原因通常是它可以更好地与底层硬件配合使用。如果您 do 使用 #pragma pack__attribute__((packed)) 或其他任何东西,您可能会受到性能影响,因为编译器 - 或内存硬件 - 静默处理未对齐的访问。

仅显式存储 3 字节数组可能更好,IMO。

首先,不要使用位域或结构。它们可能会根据需要包含填充,并且位字段通常不可移植。

除非您的 CPU 明确获得 24 位算术指令 - 这似乎不太可能,除非它是一些古怪的 DSP - 然后自定义数据类型只会实现额外的堆栈膨胀。

您很可能必须对所有算术运算使用 uint32_t。这意味着您的 24 位类型在节省 RAM 方面可能不会取得太大成就。如果您发明了一些具有 setter/getter (serialization/de-serialization) 访问权限的自定义 ADT,您可能只是 浪费了 RAM,因为如果函数不能,您将获得更高的堆栈峰值使用率内联。

要真正节省 RAM,您应该修改程序设计。


也就是说,您可以基于数组创建自定义类型:

typedef unsigned char u24_t[3];

每当您需要访问数据时,您 memcpy 它 to/from 一个 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 字节。