【问题标题】:defining a function twice in C在 C 中定义一个函数两次
【发布时间】:2010-09-27 16:25:47
【问题描述】:

我有问题。我写了这段代码,啊。 a.c 和 main.c:

文件:a.h

#ifndef _a_H
#define _a_H
int poly (int a, int b, int c, int x);

int square (int x)
{
       return x*x;
}
#endif // _a_H

文件:a.c

#include "a.h"
int poly (int a, int b, int c, int x)
{
     return a*square(x) + b * x +c;
}

文件:main.c

#include <stdio.h>
#include "a.h"
int main()
{
    int p1 = poly1 (1 ,2 , 1, 5);
    int p2 = poly2 (1 ,1 , 3, 5);

    printf ("p1 = %d, p2 = %d\n", p1, p2);
    return 0;
}

我得到了一个错误:

/tmp/ccKKrQ7u.o:在函数'square'中:
main.c:(.text+0x0): 'square'的多重定义
/tmp/ccwJoxlY.o:a.c:(.text+0x0): 首先在这里定义
collect2: ld 返回 1 个退出状态

所以我将函数 square 的实现移到了 ​​a.c 文件中,它可以工作了。

有人知道为什么吗?

感谢

【问题讨论】:

    标签: c function header


    【解决方案1】:

    一般来说,*.c 文件被编译成 *.o 文件,*.o 文件被链接以构建可执行文件。 *.h 文件未编译,它们以文本形式包含在 *.c 文件中,此时它们是 #included。

    因此,通过在两个单独的 *.c 文件中 #include "a.h" 两次,您已将 square() 的定义放入两个单独的文件中。编译这些文件后,您最终会得到两个 square() 副本,每个 *.o 文件中都有一个。然后,当您链接它们时,链接器会看到这两个文件,并生成错误。

    如何避免这种情况?你已经发现了这一点。不要将函数定义放在 *.h 文件中。将它们放在*.c 文件中,并且只将函数声明放在*.h 文件中。

    【讨论】:

    • 另外,#define _a_H 对每个目标文件发生一次,因此 #ifndef _a_H 检查成功两次。
    • 通常建议内联这样的小函数。通过在 .c 文件中声明它,它们仍然必须具有由 .o 文件中的符号名称导出的标准非内联版本。在这些情况下,最好将它们定义为static inline int square (int x) 以避免它。如果发生这种情况,最好在头文件中而不是在几个 .c 文件中进行。
    【解决方案2】:

    不要将代码放在头文件中。您的两个 .c 文件都包含 a.h,因此它们都实现了 square,因此您的链接器会报错。

    【讨论】:

      【解决方案3】:

      square() 在标头中时,它同时包含在 a.c 和 main.c 中,因此每个对应的目标文件都有自己的 square(),但没有 static 修饰符,它们具有完全相同的名称。将它移到 a.c 意味着它只定义一次。

      如果你真的想要头文件中的函数,你可以用静态内联定义它:

      static inline int square (int x)
      {
             return x*x;
      }
      

      静态意味着每个包含 .h 的 .c 文件都有自己的 square() 版本,inline 意味着代码将被内联删除,实际上不会发生函数调用,这可能是你想要的这里

      【讨论】:

      • thanx :) 但有一件事我不明白,如果在头文件中我写了 #ifndef _a_H #define _a_H 等,它应该知道不要包含它两次,所以如果它是在 a.c 中定义的在 main.c 中,所以它应该只包含一次。不?我错了吗?
      • 标头保护防止它在同一个文件(或“编译单元”)中包含两次。您正在编译两个单独的 C 文件,因此每个文件都将包含标头,并且每个文件都将被正方形编译到生成的对象中。
      • 顺便说一句,GCC(可能还有其他编译器)并不总是内联标有inline 的函数,有时内联静态函数未明确标记为内联。但是,inline 在定义静态函数但未使用时防止编译器警告。因此,static inline 是直接在头文件中定义微小函数的方式。
      • @Matt:main.c 和 a.c 在两个独立的编译器运行中编译(生成 main.o 和 a.o)。您的包含守卫保证每次运行时只包含一次标头,但每次编译仍然包含一次标头。
      • @Matt - 符号 _a_H 仅定义给 C 预处理器,而不是 C 编译器(它们可能是相同的可执行文件,但在逻辑上它们是不同的)。当您编译多个文件时,预处理器会启动每个未定义符号的源文件,因此 main.c 是否定义了 _a_H 无关紧要。当预处理器到达 a.c 时,它将不再有 main.c 中定义的任何预处理器符号。
      【解决方案4】:

      这是因为您的 C 编译器将每个 .c 文件构建为一个对象 (.o) 文件,然后链接这些对象文件以生成可执行文件。 .c 文件的内容及其包含的所有文件称为编译单元

      您的示例有两个编译单元:

      • main.c,包括stdio.ha.h → 编译为main.o
      • a.c,包括a.h → 编译为a.o

      然后链接器 (ld) 尝试链接 .o 文件,但发现它们都定义了 square(),因为它位于共享的 a.h 中——因此出现错误。这就是为什么正如一些人已经指出的那样,您不应该将函数定义放在标题中。

      如果您安装了nm 实用程序(您应该安装),您可以运行

      $ nm main.o
      $ nm a.o
      

      在 shell 中查看square 存在于两个文件中。

      (编辑:我想到的术语实际上是翻译单元,但在搜索时,它们似乎意味着几乎相同的东西。)

      【讨论】:

      • 我不想用太多额外的信息来混淆我的答案,但值得补充的是,构建 C 程序的步骤通常是 (1) 预处理; (2) 编译; (3) 对于某些系统,组装; (4)链接。整个过程通常被混淆地称为“编译”,但这里的答案说“编译”,它们具体指的是步骤 (2)。
      • +1 了解更多细节,提到 nm,我从未听说过,这很酷。
      【解决方案5】:

      您不能在头文件中编写实现(函数体),否则链接器将找到对该函数的多个引用。

      通常,在头文件中只放置声明,而不是定义。

      【讨论】:

        【解决方案6】:

        您已经回答了自己的问题:您已经定义了两次 square,一次在每个包含 a.h 的编译文件中。

        为避免这种情况,您可以将 square 设为静态函数

        static int square (int x)
        {
           return x*x;
        }
        

        并添加编译器使用的任何内联提示,例如inline__inline 也是。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-08-28
          • 2012-06-04
          • 2020-12-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多