it-swarm.cn

为什么不能在C中在C ++中的头文件中包含方法定义?

在C语言中,您不能在头文件中包含函数定义/实现。但是,在C++中,您可以在头文件中具有完整的方法实现。为什么行为不同?

25
Joshua Partogi

在C语言中,如果在头文件中定义一个函数,则该函数将出现在每个编译的包含该头文件的模块中,并且将为该函数导出公共符号。因此,如果在header.h中定义了功能additup,并且foo.c和bar.c都包含header.h,则foo.o和bar.o都将包含additup的副本。

当您将这两个目标文件链接在一起时,链接器将看到符号additup被定义了多次,并且不允许这样做。

如果您声明该函数为静态函数,则不会导出任何符号。目标文件foo.o和bar.o仍将包含该函数代码的单独副本,并且它们将能够使用它们,但是链接器将看不到该函数的任何副本,因此它不会抱怨当然,其他模块也无法看到该功能。并且您的程序将被两个具有相同功能的相同副本所肿。

如果仅在头文件中声明该函数,但未定义它,然后仅在一个模块中对其进行定义,则链接器将看到该函数的一个副本,并且程序中的每个模块都将能够看到它,并且用它。并且您的编译程序将仅包含该函数的一个副本。

因此,can在C的头文件中具有函数定义,这只是不好的样式,不好的形式和全面的坏主意。

(通过“声明”,我的意思是提供没有主体的函数原型;通过“定义”,我的意思是提供函数主体的实际代码;这是标准的C术语。)

29
David Conrad

在这方面,C和C++的行为非常相似-您可以在标头中使用inline函数。在C++中,主体位于类定义内的任何方法都隐式inline。如果要在C中执行相同的操作,请声明static inline

30
Simon Richter

头文件的概念需要一些解释:

您可以在编译器的命令行上提供文件,也可以执行“ #include”。大多数编译器都将扩展名为c,C,cpp,c ++等的命令文件作为源文件。但是,它们通常包括一个命令行选项,以启用对源文件的任意扩展名的使用。

通常,命令行上给出的文件称为“源”,其中包含的文件称为“标头”。

预处理器步骤实际上将所有内容都带走了,并使所有内容在编译器中看起来像一个大文件。此时,标题或源中的内容实际上并不重要。通常有一个编译器选项可以显示此阶段的输出。

因此,对于在编译器命令行上给出的每个文件,都将一个巨大的文件分配给编译器。这可能具有将占用内存和/或创建要从其他文件引用的符号的代码/数据。现在,每一个将生成一个“对象”图像。如果在两个以上链接在一起的目标文件中找到相同的符号,则链接器可以给出“重复符号”。也许这就是原因;不建议将代码放在头文件中,头文件可以在目标文件中创建符号。

“内联”通常是内联的..但是在调试时,它们可能不会内联。那么,链接器为什么不给出乘法定义的错误?简单...这些是“弱”符号,只要来自所有对象的弱符号的所有数据/代码都具有相同的大小和内容,链接将保留一个副本并从其他对象中删除副本。有用。

7
vrdhn

您可以在C99中执行此操作:保证在其他地方提供inline函数,因此,如果未内联函数,则将其定义转换为声明(即,放弃实现)。而且,当然,您可以使用static

4
SK-logic

C++标准引号

C++ 17 N4659标准草案 10.1.6“内联说明符”表示方法是隐式内联的:

4在类定义中定义的函数是内联函数。

然后再往下看,我们不仅可以在所有翻译单元上定义内联方法,而且必须定义:

6内联函数或变量应在使用过的每个翻译单元中定义,并且在每种情况下均应具有完全相同的定义(6.2)。

在12.2.1“成员函数”的注释中也明确提到了这一点:

1成员函数可以在其类定义中定义(11.4),在这种情况下,它是内联成员函数(10.1.6)[...]

3 [注意:程序中最多可以有一个非内联成员函数的定义。一个程序中可能有多个内联成员函数定义。参见6.2和10.1.6。 —尾注]

GCC 8.3实现

main.cpp

struct MyClass {
    void myMethod() {}
};

int main() {
    MyClass().myMethod();
}

编译和查看符号:

g++ -c main.cpp
nm -C main.o

输出:

                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 W MyClass::myMethod()
                 U __stack_chk_fail
0000000000000000 T main

然后我们从man nm表示MyClass::myMethod符号在ELF目标文件上标记为弱,表示它可以出现在多个目标文件上:

“ W”“ w”该符号是弱符号,尚未专门标记为弱对象符号。当弱定义符号与普通定义符号链接时,使用普通定义符号不会出错。当链接了一个未定义的弱符号而未定义该符号时,该符号的值将以系统特定的方式确定,而不会出现错误。在某些系统上,大写表示已指定默认值。