简体   繁体   English

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

[英]How to create 24 bit unsigned integer in C

I am working on an embedded application where RAM is extremely tight.我正在开发一个 RAM 非常紧张的嵌入式应用程序。 For this purpose I need to create a 24 bit unsigned integer data type.为此,我需要创建一个 24 位无符号 integer 数据类型。 I am doing this using a struct:我正在使用结构执行此操作:

typedef struct
{
    uint32_t v : 24;
} uint24_t;

However when I interrogate the size of a variable of this type, it returns "4", ie:但是,当我询问这种类型的变量的大小时,它返回“4”,即:

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

Is there a way I can force this variable to have 3 bytes?有没有办法可以强制这个变量有 3 个字节?

Initially I thought it was because it is forcing datatypes to be word aligned, but I can for example do this:最初我认为这是因为它强制数据类型是字对齐的,但我可以例如这样做:

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

And in that case the size comes out at 3.在这种情况下,大小为 3。

Well, you could try to ensure that the structure only takes up the space you need, with something like:好吧,您可以尝试确保该结构仅占用您需要的空间,例如:

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

You may have to provide those compiler directives (like the #pragma lines above) to ensure there's no padding but this will probably be the default for a structure with only eight-bit fields (a) .可能必须提供那些编译器指令(如上面的#pragma行)以确保没有填充,但这可能是只有八位字段(a)的结构的默认设置。

You would probably then have to pack/unpack real values to and from the structure, something like:然后,您可能必须在结构中打包/解包实际值,例如:

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

Note that this gives you big-endian values regardless of your actual architecture.请注意,无论您的实际架构如何,这都会为您提供大端值。 This won't matter if you're exclusively packing and unpacking yourself, but it may be an issue if you want to use the memory blocks elsewhere in a specific layout (in which case you can just change the pack/unpack code to use the desired format).如果您只自己打包和解包,这无关紧要,但如果想在特定布局的其他地方使用 memory 块(在这种情况下,您只需更改打包/解包代码以使用所需的格式)。

This method adds a little code to your system (and a probably minimal performance penalty) so you'll have to decide if that's worth the saving in data space used.此方法向您的系统添加了一些代码(并且可能会降低性能损失),因此您必须决定是否值得节省使用的数据空间。


(a) For example, both gcc 7.3 and clang 6.0 show 3 6 for the following program, showing that there is no padding either within or following the structure: (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;
}

However, that is just a sample so you may want to consider, in the interest of portable code, using something like #pragma pack(1) , or putting in code to catch environments where this may not be the case.但是,这只是一个示例,因此出于可移植代码的考虑,您可能需要考虑使用#pragma pack(1)之类的东西,或者放入代码以捕获可能不是这种情况的环境。

A comment by João Baptista on this site says that you can use #pragma pack . João Baptista此站点上的评论说您可以使用#pragma pack Another option is to use __attribute__((packed)) :另一种选择是使用__attribute__((packed))

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

This should work on GCC and Clang.这应该适用于 GCC 和 Clang。

Note, however, that this will probably screw up alignment unless your processor supports unaligned access.但是请注意,除非您的处理器支持非对齐访问,否则这可能会搞砸 alignment。

Initially I thought it was because it is forcing datatypes to be word aligned最初我认为这是因为它强制数据类型是字对齐的

Different datatypes can have different alignment.不同的数据类型可以有不同的 alignment。 See for example the Objects and alignment doc.例如,参见Objects 和 alignment文档。

You can use alignof to check, but it's totally normal for char or uint8_t to have 1-byte (ie, effectively no) alignment, but for uint32_t to have 4-bye alignment.您可以使用alignof进行检查,但charuint8_t具有 1 字节(即实际上没有)alignment 是完全正常的,但uint32_t具有 4 字节 alignment。 I don't know if the alignment of bitfields is explicitly described, but inheriting it from the storage type seems reasonable enough.我不知道位域的 alignment 是否被明确描述,但是从存储类型继承它似乎足够合理。

NB.注意。 The reason for having alignment requirements is generally that it works better with the underlying hardware.有 alignment 要求的原因通常是它与底层硬件配合得更好。 If you do use #pragma pack or __attribute__((packed)) or whatever, you may take a performance hit as the compiler - or the memory hardware - silently handle misaligned accesses.如果您确实使用#pragma pack__attribute__((packed))或其他任何东西,您可能会因为编译器 - 或 memory 硬件 - 默默地处理未对齐的访问而受到性能影响。

Just explicitly storing a 3-byte array is probably better, IMO. IMO,仅显式存储一个 3 字节数组可能会更好。

To begin with, don't use bit-fields or structs.首先,不要使用位域或结构。 They may include padding as they please and bit-fields are non-portable in general.它们可以随意包含填充,并且位域通常是不可移植的。

Unless your CPU explicitly got 24 bit arithmetic instructions - which doesn't seem very likely unless it's some oddball DSP - then a custom data type will achieve nothing but extra stack bloat.除非您的 CPU 明确获得 24 位算术指令——除非它是一些奇怪的 DSP,否则这似乎不太可能——那么自定义数据类型只会实现额外的堆栈膨胀。

Most likely you will have to use uint32_t for all arithmetic.您很可能必须对所有算术使用uint32_t Meaning that your 24 bit type might not achieve much when it comes to saving RAM.这意味着您的 24 位类型在节省 RAM 方面可能效果不佳。 If you invent some custom ADT with setter/getter (serialization/de-serialization) access, you are probably just wasting RAM since you get higher stack peak usage if the functions can't be inlined.如果您发明了一些具有 setter/getter(序列化/反序列化)访问的自定义 ADT,那么您可能只是在浪费RAM,因为如果无法内联函数,您将获得更高的堆栈峰值使用率。

To actually save RAM you should rather revise your program design.要真正节省 RAM,您应该修改程序设计。


That being said, you can create a custom type based on an array:话虽如此,您可以基于数组创建自定义类型:

typedef unsigned char u24_t[3];

Whenever you need to access the data, you memcpy it to/from a 32 bit type and then do all arithmetic on 32 bits:每当您需要访问数据时,您可以将其memcpy到/从 32 位类型,然后在 32 位上执行所有算术:

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

But note that this assumes little endian, since we are only working with bits 0 to 2. In case of a big endian system, you will have to do memcpy((uint8_t*)&u32+1, ... to discard the MS byte.但请注意,这是假设小端,因为我们只使用位 0 到 2。在大端系统的情况下,您将不得不执行memcpy((uint8_t*)&u32+1, ...丢弃 MS 字节.

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM