【问题标题】:Preprocessor Macro Explanation?预处理器宏解释?
【发布时间】:2012-12-02 02:53:13
【问题描述】:

我的老师让我们使用以下预处理器宏来实现 C 中的链接队列。这个想法是通过让队列不包含数据来使队列通用,然后在其他地方有一个包装器结构,用于保存队列中的节点以及属于它的数据。

下面的宏从队列中获取一个节点(即node),wrapper结构的类型(即struct Wrapper),以及Wrapper的队列节点元素的名称(即qnode(Wrapper有一个叫做qnode的元素) ))。

然后宏返回传入的节点所在的struct Wrapper。

所以调用看起来像这样:

queue_entry(node, struct Wrapper, qnode)

这在我看来非常酷,而且效果很好,(看看我的老师是怎么写的,最好!)。 但我希望有人可以向我解释它实际上是如何工作的?因为我对幕后实际发生的事情一无所知。

宏:

#define queue_entry(NODE, STRUCT, MEMBER)               \
    ((STRUCT *)((uint8_t*)(NODE) - offsetof(STRUCT, MEMBER)))

【问题讨论】:

  • 显然他/她从来没有学会习惯将宏大写,如果你把它看成一个函数,并且将像 i++ 这样的参数传递给宏,就会发生不好的事情
  • 很高兴知道!我们说的是全部大写吗?
  • 是的,宏名通常写成ALL_CAPS

标签: c struct linked-list c-preprocessor


【解决方案1】:

C 结构中的成员元素的地址有固定的差异。这种差异是在编译时定义的。

您可以通过使用&(struct_object.member) - &struct_object 获得这种地址差异。这是offsetof 返回的内容。

例如考虑下面的结构:

struct abcd{
int a; // 4 bytes
int b; // another 4 bytes
char c;// 1 byte
}

然后offsetof(struct abcd,c) 将返回 8。offsetof(struct abcd,a) 将是 0 等等。结构中的“padding”或“对齐”也起到决定偏移的作用。但是,这是在编译时决定的,而不是在运行时决定的。

因此,如果您有成员的地址(在我们的示例中为 char c),但没有结构,则可以通过从成员地址中减去成员地址偏移量来获得父结构的地址。

在您的示例中,成员的地址包含在节点中。因此,如果您减去成员偏移量,您将获得容器结构的地址。

在 linux 内核源代码中,同样的宏在名称 container_of 下可用(我忘记了大写和下划线,如果有的话)。

【讨论】:

  • 说得通!感谢您的精彩解释。你是说 container_of 做同样的事情吗?
  • 几乎相同,只是在您的情况下它将 member 作为参数而不是 &member。
【解决方案2】:

把宏想象成 C 的 sed

您会看到 QUEUE_ENTRY(a, b, c) 的任何地方:

((b *)((uint8_t*)(a) - offsetof(b, c)))

所有这些替换都是在之前编译完成

【讨论】:

  • 所以我从来没有使用过 sed...我只知道它可以让您编辑文本流...但是让我看看我是否理解您在说什么...宏去了并在运行时之前用内部代码替换对 QUEUE_ENTRY 的引用,然后该内部代码在算术上作为指向我想要的节点的指针。
  • 差不多,它被称为预处理器。所以如果你有一个宏 ABS(x) (x<0)?-x:x 并且你像函数一样使用它并传递 index++,你会得到 (index++<0)?-index++:index++ 并且当你传递它时 index 的值为 6,那么你会得到 8 而不是你会得到的 6获取它是否是一个函数。因此,用大写区分它们的重要性。编译实际上是 4 个部分 - 将 .c 和 .h 预处理为中间 .i 文件,将 .i 文件编译为 .s 汇编文件,然后将 .s 文件汇编为 .o 目标文件……最后链接 .o 文件进入 .so 库或二进制文件
猜你喜欢
  • 2015-04-15
  • 2015-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-19
  • 2020-02-09
  • 1970-01-01
相关资源
最近更新 更多