使用宏转换结构中整数字段的字节顺序

Convert endianness of integer fields in struct using macros

考虑以下结构和函数

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++)。

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); };

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

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

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

此示例代码中的宏提取自 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);
}

这使得处理未对齐的内存访问以及任何系统上的网络字节排序变得更加容易。基于二进制的数学使得它既便携又 space 高效。

编辑 (X-macros)

根据 Lightness Races in Orbit 提出的评论和疑虑,这里有一个 header 文件 X-macros 可用于自动创建 X_read / X_write内联函数。

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

在此示例中,相同的 header 被多次包含,但结果不同。此外,read/write 函数不必内联,这只是一个示例。

这是header:

/* 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 次 header。

嗯,这很简单。

#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 添加了自定义数组类型示例和一些行顺序更改。