【问题标题】:Confusion about inline declarations in C关于 C 中的内联声明的困惑
【发布时间】:2011-10-18 20:03:46
【问题描述】:

我正在用 C 实现队列的实现。我的界面包含五个简单的函数来访问队列:

#ifndef QUEUE_H
#define QUEUE_H

#include <stdbool.h>
#include <stddef.h>

struct queue {
  struct cell* first;
  struct cell* last;
};

typedef struct queue queue;

extern queue newQueue(void);
extern bool  isEmpty(queue);
extern queue enqueue(queue,void*);
extern queue dequeue(queue);
extern void* front(queue);
extern void  freeQueue(queue);

由于其中两个(newQueueisEmpty)非常简单,我相信编译器可以对它们进行很多很好的优化,因此我决定为它们编写内联声明:

/* replacing the two lines

extern queue newQueue(void);
extern bool  isEmpty(queue);

in the original header */

extern inline queue newQueue(void) {
  queue q = { NULL, NULL };
  return q;
}

extern inline bool isEmpty(queue q) {
  return q.first == NULL;
}

使用 gcc 可以很好地编译。但是当我用clang编译它时,它给了我一个错误。一项快速研究表明,从 GNU 样式执行这些内联声明 is different 的官方方式。我可以通过-std=gnu89 或根据上面的链接更改函数签名。我选择了第二个选项:

inline queue newQueue(void) {
  queue q = { NULL, NULL };
  return q;
}

inline bool isEmpty(queue q) {
  return q.first == NULL;
}

但是现在,当在 c99 模式下编译时,clang 和 gcc 都谈到了重复的函数声明。这是queue.c中的随附定义:

#include "queue.h"

/* ... */

queue newQueue() {
  queue q = { NULL, NULL };
  return q;
}

bool isEmpty(queue q) {
  return q.first == NULL;
}

我做错了什么?如何在不需要切换到 gnu89 模式的情况下得到我想要的?

这些是我使用第二种样式时收到的错误消息:

$ gcc -std=c99 queue.c 
queue.c:12:7: error: redefinition of ‘newQueue’
queue.h:14:21: note: previous definition of ‘newQueue’ was here
queue.c:17:6: error: redefinition of ‘isEmpty’
queue.h:19:20: note: previous definition of ‘isEmpty’ was here
$ clang -std=c99 queue.c
queue.c:12:7: error: redefinition of 'newQueue'
queue newQueue() {
      ^
In file included from queue.c:5:
./queue.h:14:21: note: previous definition is here
extern inline queue newQueue(void) {
                    ^
queue.c:17:6: error: redefinition of 'isEmpty'
bool isEmpty(queue q) {
     ^
In file included from queue.c:5:
./queue.h:19:20: note: previous definition is here
extern inline bool isEmpty(queue q) {
                   ^
2 errors generated.

【问题讨论】:

  • 您能否显示您收到的exact 错误消息? “关于重复函数定义的一些事情”不够具体。
  • @Greg 抱歉忘记了错误信息。我已经添加了。

标签: c gcc inline c99 clang


【解决方案1】:

如果您在标题中定义函数,请将它们设为static。这应该足以让编译器内联它们(inline 只是一个额外的提示)。

如果您在整个程序中多次包含该标头,标头中的非 static 函数将导致多个定义。

我做了一些研究并获得了更多信息:

您可以这样使用inline。至少在 C99 中。您不能在queue.c 中同时拥有内联和非内联定义。您需要在#ifdef 中包装内联定义或将它们移动到未包含在queue.c 中的标题中。

您需要编写两次函数并使用预处理器,但它应该完全按照您的意愿工作。当函数没有被内联时,它只会被发射一次。

【讨论】:

  • 在将函数定义为static 的情况下,是不是该函数最终会在我的可执行文件中被编译多次?有没有办法确保如果一个函数真的生成了,那么只生成一次?
  • @FUZxxl 你为什么在乎?生成足够体面的可执行文件是工具集的工作。编译器和链接器可以合作完成该任务。使用 clang/llvm,您还可以进行链接时优化,以优化整个程序,而不仅仅是单个模块。无论从中获得什么收益,都会使由于重复功能造成的任何“损失”变得无关紧要。或者,换句话说,除非你能衡量它有影响,否则不要担心这些事情。这不是过早的悲观。
  • @KubaOber 并不是所有的工具集都足够聪明,可以确定不同翻译单元中的两个函数是相等的。在大多数工具链(包括 GNU 工具链)中,这样的事情可能是不可能的,因为重要信息不可用(例如,函数在哪里结束)。我不想为一个工具链编写代码,我想编写可移植的代码,它可以在任何地方工作,并且在任何地方都尽可能高效。这不包括对特定工具链功能的任何假设。
  • 如果函数是inline,那么它可能会在每次调用时复制到您的可执行文件中——这就是inline 的意思。因此,将其设为static inline 并没有“额外的伤害”。
  • @FUZxxl 再说一次,你为什么关心输出中会有一些重复的函数?如果必须在可执行文件上运行 upx,但实际上,有什么大不了的?
【解决方案2】:

您不应该在 queue.c 中声明它们

【讨论】:

  • 这不是真的。他不应该在 queue.c 中定义它们,但是根据标准 c 内联规则(与 GCC 之前的 c99 内联行为相反),您需要有一个 extern 声明(大多数方便地在相应的 .c 文件中),或制作内联函数static。我不相信许多编译器可以删除由static inline 引起的重复代码,因此将extern 声明放在 queue.c 中可能是他想要的最佳选择。
【解决方案3】:

在 c99 及更高版本中执行此操作的正确方法是仅在 .c 文件中使用内联函数的外部声明,而不是对其进行定义。这将强制为该函数创建独立代码,以便在由于某种原因无法进行内联时正确链接。见:http://www.greenend.org.uk/rjk/tech/inline.html

由于函数默认为extern,这就足够了:

queue.c

#include "queue.h"

/* ... */

queue newQueue();

bool isEmpty(queue q);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多