2

我正在编写网络层协议,需要找到用 C 定义的压缩结构的大小。因为编译器可能会添加额外的填充字节,这sizeof在我的情况下使函数无用。我查了一下谷歌,发现我们可以使用___attribute(packed)___这样的东西来防止编译器添加额外的填充字节。但我相信这不是可移植的方法,我的代码需要同时支持 windows 和 linux 环境。

目前,我已经定义了一个宏来映射我的代码中定义的每个结构的打包大小。考虑下面的代码:

typedef struct {
...
} a_t;

typedef struct {
...
} b_t;

#define SIZE_a_t 8;
#define SIZE_b_t 10;

#define SIZEOF(XX) SIZE_##XX;

然后在主函数中,我可以使用上面的宏定义如下:-

int size = SIZEOF(a_t);

这种方法确实有效,但我相信它可能不是最好的方法。有关如何在 C 中有效解决此问题的任何建议或想法?

例子

考虑下面的 C 结构:-

typedef struct {
   uint8_t  a;
   uint16_t b;
} e_t;

在 Linux 下,sizeof函数返回 4 个字节而不是 3 个字节。为了防止这种情况,我目前正在这样做:-

typedef struct {
   uint8_t  a;
   uint16_t b;
} e_t;

#define SIZE_e_t 3
#define SIZEOF(XX) SIZE_##e_t

现在,当我调用SIZEOF(e_t)我的函数时,它应该返回 3 而不是 4。

4

4 回答 4

4

sizeof 查找结构或任何其他 C 数据类型大小的可移植方法。

您面临的问题是如何确保您的结构具有您需要的大小和布局

#pragma pack或者__attribute__((packed))很可能为您完成这项工作。它不是 100% 可移植的(在 C 标准中没有提到打包),但它可能对于您当前的目的来说足够可移植,但请考虑您的代码将来是否需要移植到其他平台。它也可能不安全。看到这个问题这个答案

唯一 100% 可移植的方法是使用数组unsigned char并跟踪哪些字段占用哪些字节范围。当然,这要麻烦得多。

于 2012-05-26T23:47:50.587 回答
2

你的宏告诉你你认为结构应该有的大小,如果它已经按照你的意图布局。

如果这不等于sizeof(a_t),那么您编写的任何认为它已打包的代码都将无法正常工作。假设它们是相等的,您不妨将其sizeof(a_t)用于所有目的。如果它们不相等,那么您应该仅将它用于某种检查 that SIZEOF(a_t) == sizeof(a_t),这将失败并阻止您的非工作代码编译。

因此,您不妨将检查放在头文件中sizeof(a_t) == 8,而不必费心定义SIZEOF.

除此之外,它的SIZEOF行为并不像sizeof. 例如考虑typedef a_t foo; sizeof(foo);,这显然不适用于SIZEOF

于 2012-05-26T23:16:08.773 回答
1

我不认为手动指定大小比使用 sizeof 更便携。

如果大小更改,您的 const 指定大小将是错误的。

打包的属性是可移植的。在 Visual Studio 中,它是#pragma pack

于 2012-05-26T23:10:36.783 回答
1

我建议不要尝试通过将数据覆盖在结构上来读取/写入数据。我建议改为编写一系列在概念上类似于 printf/scanf 的例程,但它们使用指定二进制数据格式的格式说明符。我建议不要使用基于百分号的标签,而是简单地使用数据格式的二进制编码。

有几种方法可以采取,包括在序列化/反序列化例程本身的大小、使用它们所需的代码大小以及处理各种反序列化格式的能力之间进行权衡。最简单(也是最容易移植)的方法是使用例程,而不是使用格式字符串,而是通过采用双间接指针来单独处理项目,从中读取一些数据类型,并适当地增加它。因此:

uint32_t read_uint32_bigendian(uint8_t const ** src)
{
  uint8_t *p;
  uint32_t 时间;

  p = *src;
  tmp = (*p++) << 24;
  tmp |= (*p++) << 16;
  tmp |= (*p++) << 8;
  tmp |= (*p++);
  *src = p;
}

...
  字符缓冲区[256];
...
  uint8_t *buffptr = buff;
  first_word = read_uint32_bigendian(&buffptr);
  next_word = read_uint32_bigendian(&buffptr);

这种方法很简单,但缺点是打包和解包代码有很多冗余。添加格式字符串可以简化它:

#define BIGEND_INT32 "\x43" // 或者任何合适的标记
  uint8_t *buffptr = buff;
  read_data(&buffptr, BIGEND_INT32 BIGEND_INT32, &first_word, &second_word);

这种方法可以通过一个函数调用读取任意数量的数据项,buffptr只传递一次,而不是每个数据项一次。在某些系统上,它可能仍然有点慢。另一种方法是传入一个字符串,指示应该从源接收哪种数据,然后还传入一个字符串或结构,指示数据应该去哪里。这可以允许通过单个调用解析任意数量的数据,为源提供一个双间接指针,一个指示源数据格式的字符串指针,一个指示如何解包数据的结构的指针,以及aa 指向保存目标数据的结构的指针。

于 2012-05-27T17:40:22.223 回答