在C中查找打包结构大小的便携方式

时间:2012-05-26 23:07:51

标签: c struct sizeof

我正在编码网络层协议,并且需要找到C中定义的打包结构的大小。由于编译器可能会添加额外的填充字节,这使sizeof函数在我的情况下无用。我查了一下Google,发现我们可以使用这样的___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;

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

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 个答案:

答案 0 :(得分:4)

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

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

#pragma pack__attribute__((packed))可能会为您完成这项工作。它不是100%便携式(没有提到C标准中的包装),但它可能足够便于您当前的用途,但要考虑您的代码是否可能需要移植到其他平台在将来。它也可能不安全;请参阅this questionthis answer

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

答案 1 :(得分:2)

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

如果那不等于sizeof(a_t),那么无论如何,你编写的认为包装的代码都不会起作用。假设它们是平等的,你可以将sizeof(a_t)用于所有目的。如果它们不相等,那么您应该仅将它用于SIZEOF(a_t) == sizeof(a_t)的某种检查,这将会失败并阻止您的非工作代码编译。

因此,您可以将检查放在sizeof(a_t) == 8的头文件中,而不是定义SIZEOF

除了SIZEOF实际上不像sizeof这样的事实之外,这一切都是如此。例如,考虑typedef a_t foo; sizeof(foo);,这显然不适用于SIZEOF

答案 2 :(得分:1)

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

如果更改了尺寸,则const指定的尺寸将是错误的。

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

答案 3 :(得分:1)

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

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

uint32_t read_uint32_bigendian(uint8_t const ** src)
{
  uint8_t *p;
  uint32_t tmp;

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

...
  char buff[256];
...
  uint8_t *buffptr = buff;
  first_word = read_uint32_bigendian(&buffptr);
  next_word = read_uint32_bigendian(&buffptr);

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

#define BIGEND_INT32 "\x43"  // Or whatever the appropriate token would be
  uint8_t *buffptr = buff;
  read_data(&buffptr, BIGEND_INT32 BIGEND_INT32, &first_word, &second_word);

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