用于使用公共字段

时间:2017-01-18 15:58:32

标签: c pointers struct

我有许多结构,前面有前3个字段,下面是 简化示例:

struct my_struct1 {
   /* common fields. */
   int a;
   int b;
   int c;
};

struct my_struct2 {
   int a;
   int b;
   int c;
   uint32_t d;
   uint32_t e;
};

struct my_struct3 {
   int a;
   int b;
   int c;
   uint16_t d;
   char e;
};

static void func1(struct my_struct1 *s)
{
   /* ... */
}

static void func2(struct my_struct2 *s)
{
   /* ... */
}

static void func3(struct my_struct3 *s)
{
   /* ... */
}

int main(void)
{
   struct my_struct1 s = {1, 2, 3};
   struct my_struct2 p = {1, 2, 3, 4, 5};
   struct my_struct3 q = {1, 2, 3, 4, 'a'};

   func1(&s);
   func2(&p);
   func3(&q);

   /* XXX */
   func3((struct my_struct3 *)&s);

   return 0;
}

s强制转换为struct my_struct3 *并传递给func3并确保在堆栈上分配的s或其他对象不会被破坏是否安全?

原因是我想编写一个带有指针的通用API,初始化公共字段(对于结构来说很常见)。另一个函数特定于my_struct*并设置其余字段。

我不确定void *是否可以解决此问题。

更新 我应该提一下,遗憾的是我不能改变结构布局,即添加一个公共部分不是一个选项,因为我使用的代码已经很老了,我不是允许改变其核心结构。

我看到的唯一丑陋的解决方法是将void *enum struct_type参数传递给generic_init函数,并基于struct_type强制转换void *适当的结构。

3 个答案:

答案 0 :(得分:2)

充实EOF和Eugene Sh的评论。已经解释过:

将my_struct1强制转换为my_struct3是不安全的,因为my_struct3有更多的成员,my_struct1和编译器根本不会警告有关访问其他成员(d和e),覆盖my_struct1背后的内容。只要my_struct1与my_struct3的开头完全对应,反过来就可以这样做。我不确定标准中是否有任何保证可以覆盖你,但我不会赌它。

在单独的结构类型中分离出公共部分的优点 如下:

  • 这减少了代码中的重复,这有利于您 允许您在一个地方更改公共代码,减少 错误的风险。
  • 编译器可以检查传递的类型 施放结构你将有效地禁用这种检查。
  • 通常的结构体不需要在结构的开头,通过使它成为结构成员,编译器可以为你找出正确的偏移量。
struct common {
    int a;
    int b;
    int c;
};

struct my_struct1 {
   struct common com;
};

struct my_struct2 {
   struct common com;
   uint32_t d;
   uint32_t e;
};

struct my_struct3 {
   struct common com;
   uint16_t d;
   char e;
};

void init_common(struct common *com)
{
   com->a = 1;
   com->b = 2;
   /* ... */
}

struct my_struct1 s = {{1, 2, 3}};
struct my_struct2 p = {{1, 2, 3}, 4, 5};
struct my_struct3 q = {{1, 2, 3}, 4, 'a'};

init_common(&s.com);
init_common(&p.com);
init_common(&q.com);

答案 1 :(得分:2)

就我可以解释标准而言,将my_struct1*类型的指针强制转换为类型为mystruct_3*或反向的指针可能会因为指针转换而产生未定义的行为规则(参见C11 standard ISO/IEC 9899:TC2):

  

6.3.2.3指针...(7)指向对象或不完整类型的指针可以转换为指向不同对象或不完整类型的指针。如果   结果指针未正确对齐指向   类型,行为是未定义的。 ...

因此,由于my_struct1my_struct3可能具有不同的对齐方式,因此根据my_struct1正确对齐的指针未必根据my_struct3正确对齐。< / p>

但是,即使你可以保证所有结构具有相同的对齐方式,在我看来,将指针传递给my_struct1func3类型的对象是 - 不安全,即使公共成员是每个结构中的第一个成员,即使func3只访问公共成员。 原因是编译器可能会在成员之间引入填充:

  

6.7.2.1结构和联合说明符...(13)在结构对象中,非位字段成员和位域的单位   驻留的地址按其顺序增加   声明。指向结构对象的指针,适当转换,指向   到它的初始成员(或者如果该成员是位字段,那么到   它居住的单位),反之亦然。 可能有未命名的   在结构对象中填充,但不在其开头。

因此,由于my_struct1my_struct3具有不同的成员集,编译器引入填充的规则可能在这两个结构之间有所不同。我认为不太可能发生这种情况,但我没有在标准中找到任何声明来保证填充 - 即使对于前三个成员 - 对于my_struct1my_struct3也是如此。

答案 2 :(得分:0)

这是半安全的。如果第一个成员相同,则可以保证指向结构的指针也是指向第一个成员的指针。从技术上讲,编译器可以在第一个成员之后插入任意填充。实际上,没有编译器会这样做,因此如果两个结构共享第一个和第二个成员,则指向第二个成员的指针也具有相同的偏移量。但是,对于您的成员“b”,偏移量可能不是地址+ sizeof(int)。为了提高性能,整数可以填充到8个字节。

为避免歧义,您可以将公共成员显式设置为结构“common”。