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

问题描述 投票:0回答:4

我正在开发一个 RAM 非常紧张的嵌入式应用程序。 为此,我需要创建一个 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。

c struct printf typedef
4个回答
4
投票

好吧,您可以尝试确保该结构仅占用您需要的空间,例如:

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


4
投票

João Baptistathis 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。

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


2
投票

最初我认为这是因为它强制数据类型进行字对齐

不同的数据类型可以有不同的对齐方式。例如,请参阅对象和对齐文档。

您可以使用

alignof
进行检查,但
char
uint8_t
具有 1 字节(即实际上没有)对齐是完全正常的,但
uint32_t
具有 4 字节对齐。我不知道位域的对齐方式是否被明确描述,但从存储类型继承它似乎足够合理。

注意。具有对齐要求的原因通常是它与底层硬件配合得更好。如果您使用

#pragma pack
__attribute__((packed))
或其他什么,您可能会受到性能影响,因为编译器或内存硬件会默默地处理未对齐的访问。

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


0
投票

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

除非您的 CPU 明确获得 24 位算术指令(这似乎不太可能,除非它是一些奇怪的 DSP),否则自定义数据类型只会导致额外的堆栈膨胀。

很可能您必须使用

uint32_t
来进行所有算术运算。这意味着您的 24 位类型在节省 RAM 方面可能效果不佳。如果您发明一些具有 setter/getter(序列化/反序列化)访问权限的自定义 ADT,您可能只是浪费 RAM,因为如果函数无法内联,您会获得更高的堆栈峰值使用率。

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


话虽如此,您可以基于数组创建自定义类型:

typedef unsigned char u24_t[3];

每当您需要访问数据时,您都可以将其与 32 位类型进行

memcpy
转换,然后在 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 字节。

© www.soinside.com 2019 - 2024. All rights reserved.