【问题标题】:Why use address of first element of struct, rather than struct itself?为什么使用结构的第一个元素的地址,而不是结构本身?
【发布时间】:2013-11-15 14:28:56
【问题描述】:

我刚刚在工作中遇到了另一个代码库,开发人员在复制/比较/设置时始终使用结构的第一个元素的地址,而不是结构本身。这是一个简单的例子。

首先有一个结构类型:

typedef struct {
    int a;
    int b;
} foo_t;

然后有一个函数可以复制这样一个结构:

void bar(foo_t *inp)
{
    foo_t l;
    ...
    memcpy(&l.a, &inp->a, sizeof(foo_t));
    ...
}

我自己不会以这种方式编写对memcpy 的调用,我一开始怀疑最初的开发人员根本没有完全掌握 C 中的指针和结构。但是,现在我已经在两个方面看到了这一点不相关的代码库,没有共同的开发者,所以我开始怀疑自己了。

为什么要使用这种风格?

【问题讨论】:

  • 下面的答案都没有真正增加我对 C 的了解,这正是我所希望的 ;)
  • 这非常有用,特别是当多种类型的结构以相同的第一个元素开头时,作为一种伪继承。 CPython 代码库大量使用 PyObject;所有 Python 对象都以 PyObject 成员开头,并且它们通常使用 PyObject * 指针传递,因此您不必对正在使用的对象的实际类型进行硬编码。
  • 如果这是最后一个参数,memcpy 就会变得安全:sizeof(foo_t) - offsetof(foo_t, a)
  • 当我第一次掌握 C 结构和数组的窍门时,我做了很多这样的事情。我猜你对它发生的原因的看法几乎是正确的。
  • 我会从没有一个名为 l 的单字母变量开始。

标签: c struct


【解决方案1】:

取而代之的是:

memcpy(&l.a, &inp->a, sizeof(foo_t));

你可以这样做:

memcpy(&l, inp, sizeof(foo_t));

虽然这可能是危险的和误导性的,但这两个语句实际上在这里做同样的事情,因为 C 保证在第一个结构成员之前没有填充。

但最好的方法是使用简单的赋值运算符复制结构对象:

l = *inp;

为什么要使用这种风格?

我的猜测:无知或不守纪律。

【讨论】:

  • +1 表示最佳l = *inp。建议的第二佳 memcpy(&l, inp, sizeof l).
  • 无知?不太确定...我的意思是,他们知道(希望如此)这两件事是等价的并且由规范保证,所以我认为你不能推断出缺乏来自该代码的知识,但只是对此类知识的错误使用(如果经常更糟!)。
【解决方案2】:

没有人应该这样做。 如果你重新排列结构成员,你就有麻烦了。

【讨论】:

    【解决方案3】:

    一个不会。如果你曾经在结构体中移动了a 或者你在它之前插入了成员,你会引入一个内存破坏错误。

    【讨论】:

      【解决方案4】:

      此代码不安全,因为如果成员 a 不再是第一个成员,则重新排列结构的成员可能会导致 memcpy 访问超出结构的边界。

      但是,可以想象,成员在结构中是有意排序的,而程序员只想复制它们的子集,以成员 a 开始并一直运行到结构的末尾。如果是这种情况,则可以通过以下更改使代码安全:

          memcpy(&l.a, &inp->a, sizeof(foo_t) - offsetof(foo_t, a));
      

      现在可以将结构成员重新排列成任意顺序,并且 memcpy 永远不会超出范围。

      【讨论】:

        【解决方案5】:

        实际上,有一个合理的用例:构建类层次结构。

        当将结构视为类实例时,第一个成员(即偏移量 0)通常是超类型实例……如果存在超类型。这允许简单的转换在使用子类型和超类型之间移动。很有用。

        在 Darren Stone 关于意图的注释中,这是在用 C 语言执行 OO 时预期

        在任何其他情况下,我建议避免这种模式并直接访问成员,原因已经提到过。

        【讨论】:

          【解决方案6】:

          这是一个非常糟糕的习惯。例如,该结构可能有另一个成员。这是一个非常粗心的习惯,我很惊讶有人会这样做。

          其他人已经注意到了这些;让我烦恼的是:

          struct Foo rgFoo [3];
          struct Foo *pfoo = &rgFoo [0];
          

          而不是

          struct Foo *pfoo = rgfoo;
          

          为什么要通过索引取消引用数组,然后再次获取地址?已经是地址了,唯一需要注意的区别是 pfoo 在技术上是

          struct Foo *const, 
          

          不是

          struct Foo *.  
          

          但我以前总是看到第一个。

          【讨论】:

            猜你喜欢
            • 2015-12-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-12-04
            • 1970-01-01
            • 2014-12-04
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多