c - 在 C 中不使用 "PACKED"编译器指令打包数据

标签 c pragma bit-fields texas-instruments packed

背景: 它是一个 16 位 TI DSP(准确地说是 TMS320F2812)。 DSP 是小端字节序。编译器是C2000(不支持PACKED指令)。我需要通过以太网在从源到目的地的多个不同大小的结构之间进行通信。问题在于协议(protocol)需要 PACKED 数据(填充字节也将被视为信息)。

我目前计划做什么: 在结构中使用位域

typedef struct
{
   INT8U channel:4;
   INT8U priority:4;
   INT16U length;
   INT16U address;
   INT8U array[4];
} _MBXh;

疑问: 在这个特定的结构中,“INT16U长度”将从新的对齐的内存地址开始(如果我的理解错误,请纠正我)。因此,“INT8U 优先级”之后将有 (16 位 - (4+4) 位 =) 8 位的填充。

Q1 -> Does this happen even with compiler directive "pack"? (ofcourse it depends on compiler but my question is related to the c99 standard on which I could not find information). What I found was c99 provides tight packing. But with that definition, I don't understand if the "INT16U length" will start immediately after "INT8U priority" or after 8 bit padding.

Q2 -> Arrays inside structures cannot be assigned bitfields. If there are 8 bit elements inside the array then every element of the array will padded with 8 more bits to align to the 16 bit processor.

另一种替代解决方案是在传输(或接收)时使用 char* 指针指向结构体。

Q3 -> In this case I need to manually "combine" INT8U channel and INT8U priority. Which will become difficult if there are many structures declared. Please correct me if this understanding is wrong.

Q4 -> Please help me with a more elegant solution to the problem. I need to pack data (including bitfields and ARRAYS inside structures) but I do not have the compiler directive.

最佳答案

正如注释中描述的展开那样,您应该将结构注释序列化(写入时)和反序列化(读取时)到字节缓冲区或从字节缓冲区读取。

有多种方法可以做到这一点。例如,内联函数(C99 静态内联)、预处理器宏、每个字段的单独函数、位包字段的通用函数等等。

最常见的选项是从内部结构打包和解包字节数组。例如,对于内部使用的结构

struct mbxh {
    INT8U channel:4;
    INT8U priority:4;
    INT16U length;
    INT16U address;
    INT8U array[4];
};

static void pack_mbxh(unsigned char *const dst, const struct mbxh *src)
{
    dst[0] = src->channel | ((src->priority) << 4);
    dst[1] = src->length >> 8;
    dst[2] = src->length;
    dst[3] = src->address >> 8;
    dst[4] = src->address;
    dst[5] = src->array[0];
    dst[6] = src->array[1];
    dst[7] = src->array[2];
    dst[8] = src->array[3];
}

static void unpack_mbxh(struct mbxh *dst, const unsigned char *const src)
{
    dst->channel = src[0] & 15U;
    dst->priority = (src[0] >> 4) & 15U;
    dst->length = (src[1] << 8) | src[2];
    dst->address = (src[3] << 8) | src[4];
    dst->array[0] = src[5];
    dst->array[1] = src[6];
    dst->array[2] = src[7];
    dst->array[3] = src[8];
}

这特别有用,因为它使得指定字节顺序变得很简单;上面的 lengthaddress 字段使用 big-endian 或网络字节顺序。

如果目标系统的 RAM 非常有限,那么使用预处理器宏直接访问“打包”字段通常是一个不错的选择。这会使用较少的内存,但会使用更多的 CPU 资源。 (请注意,“打包”字段在这里也使用大端或网络字节顺序。)

#define mbxh_get_channel(data)  ((data)[0] & 15U)
#define mbxh_get_priority(data) ((data)[0] >> 4)
#define mbxh_get_length(data)   ((((INT16U)(data)[1]) << 8) | ((INT16U)(data)[2]))
#define mbxh_get_address(data)  ((((INT16U)(data)[3]) << 8) | ((INT16U)(data)[4]))
#define mbxh_get_array(data, i) ((data)[i])

#define mbxh_set_channel(data, value)                                 \
        do {                                                          \
            (data)[0] = ((data)[0] & 240U) | ((INT8U)(value)) & 15U); \
        } while (0)

#define mbxh_set_priority(data, value) \
        do {                           \
            (data)[0] = ((data)[0] & 15U) | (((INT8U)(value)) & 15U) << 4); \
        } while (0)

#define mbxh_set_length(data, value)            \
        do {                                    \
            (data)[1] = ((INT16U)(value)) >> 8; \
            (data)[2] = (INT8U)(value);         \
        } while (0)

#define mbxh_set_address(data, value)           \
        do {                                    \
            (data)[3] = ((INT16U)(value)) >> 8; \
            (data)[4] = (INT8U)(value);         \
        } while (0)

#define mbxh_set_array(data, index, value)    \
        do {                                  \
            (data)[(index)] = (INT8U)(value); \
        } while (0)

在实践中,特别是如果您有许多这样的结构,这些结构的组合将会起作用。首先,编写一些紧凑的函数来访问每种类型字段:低半字节、高半字节或 16 位字段,

static INT8U get4u_lo(const INT8U *const ptr)
{
    return (*ptr) & 15U;
}

static INT8U get4u_hi(const INT8U *const ptr)
{
    return (*ptr) >> 4;
}

static INT16U get16u(const INT8U *const ptr)
{
    return (((INT16U)ptr[0]) << 8) | ptr[1];
}

static void set4u_lo(INT8U *const ptr, INT8U val)
{
    *ptr &= 240U;
    *ptr |= val & 15U;
}

static void set4u_hi(INT8U *const ptr, INT8U val)
{
    *ptr &= 15U;
    *ptr |= (val % 15U) << 4;
}

static void set16u(INT8U *const ptr, INT16U val)
{
    ptr[0] = val >> 8;
    ptr[1] = val;
}

接下来,您使用上述辅助函数编写每个结构的字段访问器:

#define mbxh_get_channel(data)  get4u_lo((INT8U *)(data)+0) 
#define mbxh_get_priority(data) get4u_hi((INT8U *)(data)+0)
#define mbxh_get_length(data)   get16u((INT8U *)(data)+1)
#define mbxh_get_address(data)  get16u((INT8U *)(data)+3)
#define mbxh_get_array(data, i) ((data)[5+(i)])

#define mbxh_set_channel(data, v)  set4u_lo((INT8U *)(data)+0, (v))
#define mbxh_set_priority(data, v) set4u_hi((INT8U *)(data)+0, (v))
#define mbxh_set_length(data, v)   set16u((INT8U *)(data)+1, (v))
#define mbxh_set_address(data, v)  set16u((INT8U *)(data)+3, (v))
#define mbxh_set_array(data, i, v) ((data)[5+(i)] = (v))

与此答案中的所有示例一样,上面的数据也使用大端或网络字节顺序。 channel 位于第一个数据字节的四个低位,priority 位于第一个数据字节的四个高位。

总的来说,我建议桌面应用程序以及无论如何都使用内部结构的情况使用第一个选项(每个函数调用的结构转换)。对于微 Controller 和其他内存受限的情况,我推荐这个最新的。

(以上代码均未经过测试。如果您发现拼写错误、错误或其他错误,请在评论中通知我,以便我修复上面的示例代码。)

关于c - 在 C 中不使用 "PACKED"编译器指令打包数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41099191/

相关文章:

c++ - #pragma 警告 - 文件特定?

C++Builder 2007, union 和位字段

c - 带位域​​的 C 风格结构如何在 Rust #[repr(C)] 结构中表示?

c++ - gcc 是否有 pragma 来定义文件类型/编译器?

perl - 在使用严格之前是否有理由使用警告?

C++进程中的Java JNI泄漏

c - 回到循环 C 的开始

c - 应用于位字段的 typeof/__auto_type 的 GNU C 替换/解决方法

c - STDIO锁定: how to check if every flockfile() has been matched by funlockfile()?

c++ - 将结构作为 char 缓冲区传递给 proc 条目