如何在 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.3
和clang 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
来检查,但是char
或uint8_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 字节。
我正在开发一个 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.3
和clang 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
来检查,但是char
或uint8_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 字节。