头文件中是否包含函数定义?

3
在C/C++中,函数定义是放在头文件中还是只有声明? 以math.h中的pow()函数为例。如果函数的定义不在头文件中,那么头文件中包含什么内容才能正确链接函数定义呢?

2
这取决于——使用C++ Boost库,其中许多函数被实现为“仅头文件”代码;它们不需要单独的实现。在C语言中,在头文件中定义static inline函数是有意义的。 - Jonathan Leffler
2
这取决于——使用C++ Boost库,其中许多都被实现为“仅头文件”的代码;它们不需要单独的实现。在C中,在头文件中定义static inline函数是有意义的。 - Jonathan Leffler
2
这取决于——使用C++ Boost库,其中许多函数被实现为“仅头文件”代码;它们不需要单独的实现。在C语言中,在头文件中定义static inline函数是有意义的。 - undefined
1
对于某些编译器来说,这并不会自动发生,你需要手动链接适当的库。例如,在gcc上,你需要添加-lm来链接数学库。 - George
1
提醒:头文件中不需要包含函数定义或声明的要求。 - Thomas Matthews
显示剩余12条评论
2个回答

3
简短回答:取决于情况。
详细回答:取决于情况。
首先:什么是函数定义?
在C++中,函数既可以被声明,也可以被定义。
一个被声明的函数看起来像这样:
// <cmath>

namespace std {
    double pow(double base, double exp);
}

一个明确定义的样子是这样的:
#include <cmath>

double std::pow(double base, double exp) {
    // Do stuff...
}

不同之处在于定义的函数具有代码块(用花括号{...}表示)。因此,声明和定义的区别在于定义包含了函数的底层代码,而声明则没有任何代码。

但是假设只是声明,编译器如何知道函数的位置呢?

通常情况下,C++被编译为不同的“翻译单元”(即文件)。每个翻译单元都包括一个公共头文件(这里是<cmath>),该头文件提供了其函数的声明(这里是::std::pow)。

其中一个单元还包含::std::pow的定义。当这些单元(以目标文件或库的形式)由链接器放在一起时,对函数的所有使用都将调用在定义它的单元中的适当代码。如果未链接定义函数的单元,则链接器将报错。


请注意,只能有一个定义。如果多个单元都有自己的::std::pow的定义,链接器会因为多个定义而报错。
然而,这个“一次定义规则”并不是普遍适用的,它有以下几个例外:
  1. 内联函数(由inline存储类说明符指定),可以在多个单元中定义,但最终只保留一个定义。这也包括constexprconsteval(立即)函数,它们都隐式地是内联函数。
  2. 静态函数(由static存储类说明符指定),它们是局部于其所在的翻译单元的;即使它们具有相同的标识符,也不会与其他单元中的符号冲突。

但是::std::pow的情况实际上相当复杂。通常情况下,它会被外部定义。然而,从C++26开始,它必须是一个constexpr函数,所有这些函数都是内联的根据定义(参见cppreference)。
标准库中的许多其他函数也是如此。其中许多函数是在外部定义的(在另一个翻译单元中,例如::std::exit),但也有许多函数在其各自的头文件中定义(例如::std::is_constant_evaluated)。在其他情况下,外部函数在后续的标准版本中变成了内联函数(就像::std::pow的情况一样)。
总结一下,::std::pow 可能在 <cmath> 头文件中定义... 也可能不是。这取决于实现和标准的版本。
但一般情况是,是的,函数是在声明它们的头文件之外定义的(在另一个翻译单元中)。

1
你可能想讨论静态函数的定义或声明。 - Thomas Matthews
1
你可能想讨论静态函数的定义或声明。 - Thomas Matthews
1
你可能想讨论静态函数的定义或声明。 - undefined
@ThomasMatthews 好主意!会做的。 - epsiloneridani
@ThomasMatthews 好主意!会做的。 - epsiloneridani

1
头文件只是被预处理器包含到编译单元中的源文件。从技术上讲,它们可以包含任何C或C++代码,但在实践中,它们用于在编译单元之间公开定义/声明,同时隐藏实现细节。
在C中,您不能对同一个外部对象/函数有两个定义,因此头文件通常只包含函数声明或变量定义。
在C++中,存在类似的规则:单一定义规则。但是有一些例外,例如模板定义、类成员定义和带有"inline"说明符的函数。但是它们的定义必须在所有的编译单元中保持一致,否则行为是未定义的。
对于"pow"函数,程序可以访问它,因为默认情况下编译器链接了libc库。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接