【问题标题】:How to create 24 bit unsigned integer in C如何在 C 中创建 24 位无符号整数
【发布时间】:2020-01-20 10:31:56
【问题描述】:

我正在开发一个 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。

【问题讨论】:

  • 我不确定这是否会为您节省 RAM。 CPU 很可能是 32 位的,因此变量的每个地址通常仍然是 4 字节对齐的。
  • 如果是 24 位变量数组就不行,对吧?
  • 如果你想要一个数组,你可以使用uint8_t 并自己计算索引和打包/解包。
  • @bgarrood,数组元素在内存中是连续布局的,是的,所以如果你可以创建一个 24 位类型,那么以它作为元素类型的数组不能让所有元素在 32- 上对齐位边界。这是您的实现可能不提供定义 24 位类型的方法的一个很好的理由。但话又说回来,它可能。

标签: c struct printf typedef


【解决方案1】:

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

#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.3clang 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) 之类的东西,或者放入代码以捕获可能不存在的环境案例。

【讨论】:

  • 即使这样也不能保证uint24_t 类型只占用四个字节。允许编译器在结构布局中包含填充,并且在这种情况下可能会这样做。可能有一个扩展可用,以指示编译器不要这样做。在某些实现中,这样的东西可能拼写为 #progma pack(1) 或类似的。
  • @JohnBollinger 因为uint8_t 是1 字节对齐的,uint8t_[3] 也是如此,uint24_t 也是如此。由于uint24_t 是1 字节对齐的,最明显和最常见的情况是uint24_t 的大小确实是3
  • @John,最可能的情况是它不会填充,但是,因为它可能,我已经添加了一个注释。
  • @John,可能最广泛使用的gcc,程序sn-p uint24_t x, y[2]; printf("%zd %zd\n", sizeof(x), sizeof(y));给出3 6,显示结构内部或之后没有填充. clang 同上。可能还有其他默认情况下没有那么紧,这就是为什么我接受您的建议在答案中澄清它的原因。我也会在答案中发布详细信息。
  • @JohnBollinger:引文?我不知道有任何现代现实世界的实现过度对齐结构。
【解决方案2】:

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。

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

【讨论】:

  • 打包/控制填充是一个考虑因素,但如果您使用的是位域,则其底层存储单元的大小和数量是单独的,因此打包不一定有效果你可能会期待这里。但我发现它确实对 GCC 有这种效果,非常好。
【解决方案3】:

最初我以为是因为它强制数据类型字对齐

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

您可以使用alignof 进行检查,但charuint8_t 具有1 字节(即实际上没有)对齐是完全正常的,但uint32_t 具有4 字节对齐。不知道位域的对齐方式有没有明确描述过,但是从存储类型继承好像还是蛮合理的。

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

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

【讨论】:

    【解决方案4】:

    首先,不要使用位域或结构。它们可以随意包含填充,并且位域通常是不可移植的。

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

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

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


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

    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*)&amp;u32+1, ... 以丢弃 MS 字节。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-12
      • 1970-01-01
      • 2022-01-15
      相关资源
      最近更新 更多