在C语言中,您不能在头文件中包含函数定义/实现。但是,在C ++中,您可以在头文件中具有完整的方法实现。为什么行为不同?
在C语言中,您不能在头文件中包含函数定义/实现。但是,在C ++中,您可以在头文件中具有完整的方法实现。为什么行为不同?
Answers:
在C语言中,如果在头文件中定义函数,则该函数将出现在编译的每个包含该头文件的模块中,并且将为该函数导出公共符号。因此,如果在header.h中定义了功能additup,并且foo.c和bar.c都包含header.h,则foo.o和bar.o都将包含additup的副本。
当您将这两个目标文件链接在一起时,链接器将看到符号additup被定义了多次,并且不允许这样做。
如果您声明该函数为静态函数,则不会导出任何符号。目标文件foo.o和bar.o仍将包含该函数代码的单独副本,并且它们将能够使用它们,但是链接器将看不到该函数的任何副本,因此它不会抱怨 当然,其他模块也无法看到该功能。并且您的程序将被两个具有相同功能的相同副本所肿。
如果仅在头文件中声明该函数,但未定义它,然后仅在一个模块中对其进行定义,则链接器将看到该函数的一个副本,并且程序中的每个模块都将能够看到它,并且用它。并且您的编译程序将仅包含该函数的一个副本。
因此,您可以在C的头文件中拥有函数定义,这只是样式不好,形式不好以及全面的想法。
(通过“声明”,我的意思是提供没有主体的函数原型;通过“定义”,我的意思是提供函数主体的实际代码;这是标准的C术语。)
#ifndef HEADER_H
吗?
在这方面,C和C ++的行为非常相似-您可以inline
在标头中包含函数。在C ++中,主体位于类定义内的任何方法都是隐式的inline
。如果要在C中执行相同的操作,请声明函数static inline
。
static inline
” ...,并且在使用该函数的每个翻译单元中,您仍然会有该函数的多个副本。在具有非static
inline
功能的C ++中,您只有一个副本。要在C的标头中实际包含实现,您必须1)将实现标记为inline
(例如inline void func(){do_something();}
),并且2)实际上说此函数将位于某个特定的转换单元中(例如void func();
)。
头文件的概念需要一些解释:
您可以在编译器的命令行上提供文件,也可以执行“ #include”。大多数编译器接受扩展名为c,C,cpp,c ++等的命令文件作为源文件。但是,它们通常包括一个命令行选项,以启用对源文件的任意扩展名的使用。
通常,命令行上给出的文件称为“源”,其中包含的文件称为“标头”。
预处理器步骤实际上将所有内容都带走了,并使所有内容对于编译器而言就像一个大文件。此时,标题或源中的内容实际上并不重要。通常有一个编译器选项可以显示此阶段的输出。
因此,对于在编译器命令行上给出的每个文件,都将一个巨大的文件分配给编译器。这可能具有代码/数据,这些代码/数据将占用内存和/或创建要从其他文件引用的符号。现在,每一个将生成一个“对象”图像。如果在两个以上链接在一起的目标文件中找到相同的符号,则链接器可以给出“重复符号”。也许这就是原因;不建议将代码放在头文件中,头文件可以在目标文件中创建符号。
“内联”通常是内联的..但是在调试时,它们可能不会内联。那么,链接器为什么不给出乘法定义的错误?简单...这些是“弱”符号,只要来自所有对象的弱符号的所有数据/代码都具有相同的大小和内容,链接将保留一个副本并从其他对象中删除副本。有用。
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”该符号是弱符号,尚未专门标记为弱对象符号。当弱定义符号与普通定义符号链接时,使用普通定义符号不会出错。当链接了一个未定义的弱符号而未定义该符号时,该符号的值将以系统特定的方式确定,而不会出现错误。在某些系统上,大写表示已指定默认值。
可能出于同样的原因,您必须将完整的方法实现放入Java的类定义中。
它们看起来很相似,带有弯曲的方括号和许多相同的关键字,但是它们是不同的语言。