使用宏转换struct中整数字段的endianness

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

考虑以下结构和函数

typedef struct __attribute__((__packed__)) req_file {
  uint32_t start_pos;
  uint32_t byte_count;
  uint16_t name_len;
} req_file;

void req_file_hton(req_file *d){
  d->name_len = htons(d->name_len);
  d->start_pos = htonl(d->start_pos);
  d->byte_count = htonl(d->byte_count);
}

void req_file_ntoh(req_file *d){
  d->name_len = ntohs(d->name_len);
  d->start_pos = ntohl(d->start_pos);
  d->byte_count = ntohl(d->byte_count);
}

上面的代码很难编写很多字段的结构。我想配置一次结构的名称和字段,并为我生成函数struct_name_htonstruct_name_ntoh。我尝试过使用x宏,但运气不好。便携式C预处理器解决方案将受到高度赞赏(而不是C ++)。

c metaprogramming preprocessor x-macros
4个回答
1
投票

嗯,这很容易。

#include <stdint.h>
#include <arpa/inet.h>

/* the NETSTRUCT library ------------------------------- */

// for uint32_t
#define NETSTRUCT_dec_uint32_t(n)  uint32_t n;
#define NETSTRUCT_hton_uint32_t(n)  t->n = htonl(t->n);
#define NETSTRUCT_ntoh_uint32_t(n)  t->n = ntohl(t->n);

// for uint16_t
#define NETSTRUCT_dec_uint16_t(n)  uint16_t n;
#define NETSTRUCT_hton_uint16_t(n)  t->n = htons(t->n);
#define NETSTRUCT_ntoh_uint16_t(n)  t->n = ntohs(t->n);

// dec hton ntoh switch
#define NETSTRUCT_dec(type, name)  NETSTRUCT_dec_##type(name)
#define NETSTRUCT_hton(type, name) NETSTRUCT_hton_##type(name)
#define NETSTRUCT_ntoh(type, name) NETSTRUCT_ntoh_##type(name)

// calls NETSTRUCT_mod
#define NETSTRUCT1(mod, a)       NETSTRUCT_##mod a
#define NETSTRUCT2(mod, a, ...)  NETSTRUCT1(mod, a) NETSTRUCT1(mod, __VA_ARGS__)
#define NETSTRUCT3(mod, a, ...)  NETSTRUCT1(mod, a) NETSTRUCT2(mod, __VA_ARGS__)
#define NETSTRUCT4(mod, a, ...)  NETSTRUCT1(mod, a) NETSTRUCT3(mod, __VA_ARGS__)
// TO DO: all up to NETSTRUCT64

// variadic macro overload
#define NETSTRUCT_GET(_1,_2,_3,_4,NAME,...) NAME
// Overlads VA_ARGS with specified mod
#define NETSTRUCT_IN(mod, ...) \
        NETSTRUCT_GET(__VA_ARGS__, NETSTRUCT4, NETSTRUCT3, NETSTRUCT2, NETSTRUCT1) \
            (mod, __VA_ARGS__)

// entrypoint of out library
#define NETSTRUCT(name, ...)  \
    \
    struct name { \
        NETSTRUCT_IN(dec, __VA_ARGS__) \
    } __attribute__((__packed__)); \
    \
    void name##_hton(struct name *t) { \
        NETSTRUCT_IN(hton, __VA_ARGS__) \
    } \
    \
    void name##_ntoh(struct name *t) { \
        NETSTRUCT_IN(ntoh, __VA_ARGS__) \
    }

/* -------------------------------------------------------- */

// adding custom type
#define NETSTRUCT_dec_uint8_t_arr_8(n) uint8_t n[8];
#define NETSTRUCT_hton_uint8_t_arr_8(n) do{}while(0);
#define NETSTRUCT_ntoh_uint8_t_arr_8(n) do{}while(0);

NETSTRUCT(reg_file, 
    (uint32_t, start_pos),
    (uint32_t, byte_count),
    (uint16_t, name_len),
    (uint8_t_arr_8, example_custom_array)
);

int main() {
    struct reg_file t;
    reg_file_hton(&t);
    reg_file_ntoh(&t);
}

我写过mactos所以很容易添加另一个函数,最有可能是void name##serialize(char *in)void name##deserialize(const char *out)。设计可以稍微重构,以便类型回调NETSTRUCT_dec_*使用ex获取两个甚至未知数量的参数。 NETSTRUCT(name, (type_callback_suffix, (arguments, arguments2)))

@edit添加了自定义数组类型示例和一些行顺序更改。


2
投票

恕我直言,你应该使用原始缓冲区输入/输出。与猜测编译器在每个系统上对字段或结构进行排序的方式相比,这更容易(也更安全)。

此外,这将允许您打包/解包数据,而不必担心字节顺序或内存对齐。

此示例代码中的宏是从the facil.io framework header中提取的:

/** Reads an unaligned network ordered byte stream to a 16 bit number. */
#define fio_str2u16(c)                                                         \
  ((uint16_t)(((uint16_t)(((uint8_t *)(c))[0]) << 8) |                         \
              (uint16_t)(((uint8_t *)(c))[1])))

/** Reads an unaligned network ordered byte stream to a 32 bit number. */
#define fio_str2u32(c)                                                         \
  ((uint32_t)(((uint32_t)(((uint8_t *)(c))[0]) << 24) |                        \
              ((uint32_t)(((uint8_t *)(c))[1]) << 16) |                        \
              ((uint32_t)(((uint8_t *)(c))[2]) << 8) |                         \
              (uint32_t)(((uint8_t *)(c))[3])))

/** Writes a local 16 bit number to an unaligned buffer in network order. */
#define fio_u2str16(buffer, i)                                                 \
  do {                                                                         \
    ((uint8_t *)(buffer))[0] = ((uint16_t)(i) >> 8) & 0xFF;                    \
    ((uint8_t *)(buffer))[1] = ((uint16_t)(i)) & 0xFF;                         \
  } while (0);

/** Writes a local 32 bit number to an unaligned buffer in network order. */
#define fio_u2str32(buffer, i)                                                 \
  do {                                                                         \
    ((uint8_t *)(buffer))[0] = ((uint32_t)(i) >> 24) & 0xFF;                   \
    ((uint8_t *)(buffer))[1] = ((uint32_t)(i) >> 16) & 0xFF;                   \
    ((uint8_t *)(buffer))[2] = ((uint32_t)(i) >> 8) & 0xFF;                    \
    ((uint8_t *)(buffer))[3] = ((uint32_t)(i)) & 0xFF;                         \
  } while (0);

void req_file_read(req_file *d, unsigned char * buffer){
  d->byte_count = fio_str2u32(buffer);
  d->start_pos = fio_str2u32(buffer + 4);
  d->name_len = fio_str2u16(buffer + 8);
}

void req_file_write(unsigned char * buffer, req_file *d){
  fio_u2str32(buffer, d->byte_count);
  fio_u2str32(buffer + 4, d->start_pos);
  fio_u2str16(buffer + 8, d->name_len);
}

这使得在任何系统上处理未对齐的内存访问以及网络字节排序变得更加容易。基于二进制的数学运算使其既便携又节省空间。

EDIT (X-macros)

根据Lightness Races在Orbit中提出的评论和关注,这里有一个带有X-macro的头文件,可用于自动创建X_read / X_write内联函数。

序列化的缺点是,在使用宏声明结构时,应提供原始缓冲区的字节偏移量。

在此示例中,多次包含相同的标题,结果不同。此外,读/写功能不必内联,这只是一个例子。

这是标题:

/* note there's NO include guard in the header file */
#ifndef H__FACIL_IO_MACROS
#define H__FACIL_IO_MACROS

/** Reads an unaligned network ordered byte stream to a 16 bit number. */
#define fio_str2u16(c)                                                         \
  ((uint16_t)(((uint16_t)(((uint8_t *)(c))[0]) << 8) |                         \
              (uint16_t)(((uint8_t *)(c))[1])))

/** Reads an unaligned network ordered byte stream to a 32 bit number. */
#define fio_str2u32(c)                                                         \
  ((uint32_t)(((uint32_t)(((uint8_t *)(c))[0]) << 24) |                        \
              ((uint32_t)(((uint8_t *)(c))[1]) << 16) |                        \
              ((uint32_t)(((uint8_t *)(c))[2]) << 8) |                         \
              (uint32_t)(((uint8_t *)(c))[3])))

/** Writes a local 16 bit number to an unaligned buffer in network order. */
#define fio_u2str16(buffer, i)                                                 \
  do {                                                                         \
    ((uint8_t *)(buffer))[0] = ((uint16_t)(i) >> 8) & 0xFF;                    \
    ((uint8_t *)(buffer))[1] = ((uint16_t)(i)) & 0xFF;                         \
  } while (0);

/** Writes a local 32 bit number to an unaligned buffer in network order. */
#define fio_u2str32(buffer, i)                                                 \
  do {                                                                         \
    ((uint8_t *)(buffer))[0] = ((uint32_t)(i) >> 24) & 0xFF;                   \
    ((uint8_t *)(buffer))[1] = ((uint32_t)(i) >> 16) & 0xFF;                   \
    ((uint8_t *)(buffer))[2] = ((uint32_t)(i) >> 8) & 0xFF;                    \
    ((uint8_t *)(buffer))[3] = ((uint32_t)(i)) & 0xFF;                         \
  } while (0);

/* convert SERIAL_STRUCT_NAME to actual name */
#define SERIAL_STRUCT_MAKE(struct_name) SERIAL_STRUCT_MAKE2(struct_name)

#endif
#if SERIALIZE_TYPE /* create the type */
#undef SERIALIZE_TYPE

#undef SERIAL_STRUCT_FIELD
#define SERIAL_STRUCT_FIELD(name, bits, pos) uint##bits##_t name

#undef SERIAL_STRUCT_MAKE2
#define SERIAL_STRUCT_MAKE2(struct_name)                                       \
  typedef struct {                                                             \
    SERIAL_STRUCT_FIELDS;                                                      \
  } struct_name##_s;

/* perform macros */
SERIAL_STRUCT_MAKE(SERIAL_STRUCT_NAME)

#elif SERIALIZE_READ /* create reader function */
#undef SERIALIZE_READ

#undef SERIAL_STRUCT_FIELD
#define SERIAL_STRUCT_FIELD(name, bits, pos)                                   \
  dest->name = fio_str2u##bits((src + (pos)))

#undef SERIAL_STRUCT_MAKE2
#define SERIAL_STRUCT_MAKE2(struct_name)                                       \
  inline static void struct_name_read(struct_name##_s *dest,                   \
                                      unsigned char *src) {                    \
    SERIAL_STRUCT_FIELDS;                                                      \
  }

/* perform macros */
SERIAL_STRUCT_MAKE(SERIAL_STRUCT_NAME)

#elif SERIALIZE_WRITE /* create writer function */
#undef SERIALIZE_WRITE

#undef SERIAL_STRUCT_FIELD
#define SERIAL_STRUCT_FIELD(name, bits, pos)                                   \
  fio_u2str##bits((dest + (pos)), src->name)

#undef SERIAL_STRUCT_MAKE2
#define SERIAL_STRUCT_MAKE2(struct_name)                                       \
  inline static void struct_name##_write(unsigned char *dest,                  \
                                         struct_name##_s *src) {               \
    SERIAL_STRUCT_FIELDS;                                                      \
  }

/* perform macros */
SERIAL_STRUCT_MAKE(SERIAL_STRUCT_NAME)

#endif

在实现文件中,信息可能如下所示(同样,内联方法可能会更改):

/* will produce req_file_s as the struct name, but you can change that */
#define SERIAL_STRUCT_NAME req_file
#define SERIAL_STRUCT_FIELDS                                                   \
  SERIAL_STRUCT_FIELD(start_pos, 32, 0);                                       \
  SERIAL_STRUCT_FIELD(byte_count, 32, 4);                                      \
  SERIAL_STRUCT_FIELD(name_len, 16, 8)

#define SERIALIZE_TYPE 1
#include "serialize.h"
#define SERIALIZE_READ 1
#include "serialize.h"
#define SERIALIZE_WRITE 1
#include "serialize.h"

这可以调整,所以SERIALIZE_TYPE也声明了函数(没有定义它们),并且函数没有内联(所以只有实现文件包含每个类型3次的头文件。


1
投票

您可以调整Antony Polukhin的magic_get库,以便能够将任意(任意)结构转换为不同的字节顺序 - 就像它现在可以将任意结构打印到ostream一样。


1
投票

xmacros工作。诀窍是根据类型使用函数的标记粘贴和别名:

#define htonuint32_t htonl
#define htonuint16_t htons
#define ntohuint32_t ntohl
#define ntohuint16_t ntohl

#define DEF_FIELDS \
   DEF_FIELD(uint32_t,start_pos); \
   DEF_FIELD(uint32_t,byte_count); \
   DEF_FIELD(uint16_t,name_len)

#define DEF_FIELD(t,v)  t v

typedef struct __attribute__((__packed__)) req_file {
    DEF_FIELDS;
} req_file;

#undef DEF_FIELD
#define DEF_FIELD(t,v) d->v = hton##t(d->v)

void req_file_hton(req_file *d) {
    DEF_FIELDS;
}
#undef DEF_FIELD
#define DEF_FIELD(t,v) d->v = ntoh##t(d->v)

void req_file_hton(req_file *d) {
    DEF_FIELDS;
}

预处理代码(格式化为更清晰的显示):

typedef struct __attribute__((__packed__)) req_file {
 uint32_t start_pos;
 uint32_t byte_count;
 uint16_t name_len;
} req_file;


void req_file_hton(req_file *d) {
 d->start_pos = htonl(d->start_pos);
 d->byte_count = htonl(d->byte_count);
 d->name_len = htons(d->name_len);
}


void req_file_hton(req_file *d) {
 d->start_pos = ntohl(d->start_pos);
 d->byte_count = ntohl(d->byte_count);
 d->name_len = ntohl(d->name_len);
}

如果您有多个结构,则可以将宏系统复杂化,以便能够生成所有结构和函数。具有2种不同结构的示例:

#define htonuint32_t htonl
#define htonuint16_t htons
#define ntohuint32_t ntohl
#define ntohuint16_t ntohl

#define DEF_FIELDS_req_file \
   DEF_FIELD(uint32_t,start_pos); \
   DEF_FIELD(uint32_t,byte_count); \
   DEF_FIELD(uint16_t,name_len)

#define DEF_FIELDS_other_file \
   DEF_FIELD(uint32_t,foo_pos); \
   DEF_FIELD(uint32_t,char_count); \
   DEF_FIELD(uint16_t,bar_len)

#define STRUCT_DEF(s) \
    START_DECL(s) \
    DEF_FIELDS_##s; \
    END_DECL(s)


#define START_DECL(s) typedef struct __attribute__((__packed__)) s {
#define END_DECL(s) } s
#define DEF_FIELD(t,v)  t v

STRUCT_DEF(req_file);
STRUCT_DEF(other_file);

#undef DEF_FIELD
#undef START_DECL
#undef END_DECL
#define DEF_FIELD(t,v) d->v = hton##t(d->v)
#define START_DECL(s) void s##_hton(s *d) {
#define END_DECL(s) }

STRUCT_DEF(req_file);
STRUCT_DEF(other_file);

#undef DEF_FIELD
#undef START_DECL
#define DEF_FIELD(t,v) d->v = ntoh##t(d->v)
#define START_DECL(s) void s##_ntoh(s *d) {

STRUCT_DEF(req_file);
STRUCT_DEF(other_file);

结果:

typedef struct __attribute__((__packed__)) req_file { uint32_t start_pos; uint32_t byte_count; uint16_t name_len; } req_file;
typedef struct __attribute__((__packed__)) other_file { uint32_t foo_pos; uint32_t char_count; uint16_t bar_len; } other_file;

void req_file_hton(req_file *d) { d->start_pos = htonl(d->start_pos); d->byte_count = htonl(d->byte_count); d->name_len = htons(d->name_len); };
void other_file_hton(other_file *d) { d->foo_pos = htonl(d->foo_pos); d->char_count = htonl(d->char_count); d->bar_len = htons(d->bar_len); };

void req_file_ntoh(req_file *d) { d->start_pos = ntohl(d->start_pos); d->byte_count = ntohl(d->byte_count); d->name_len = ntohl(d->name_len); };
void other_file_ntoh(other_file *d) { d->foo_pos = ntohl(d->foo_pos); d->char_count = ntohl(d->char_count); d->bar_len = ntohl(d->bar_len); };
© www.soinside.com 2019 - 2024. All rights reserved.