为什么在 .h 文件中定义的函数在 .c 文件中重新定义?
Why is a function defined in a .h file redefined in a .c file?
我正在通读 Linux v3.19 的 PID 名称空间实现,在 pid_namespace.h
中定义了一些函数,这些函数在 pid_namespace.c
中重新定义。例如,在pid_namespace.h
中有如下定义:
static inline struct pid_namespace *copy_pid_ns(unsigned long flags,
struct user_namespace *user_ns, struct pid_namespace *ns)
{
if (flags & CLONE_NEWPID)
ns = ERR_PTR(-EINVAL);
return ns;
}
然后在 pid_namespace.c
中有第二个 copy_pid_ns
定义:
struct pid_namespace *copy_pid_ns(unsigned long flags,
struct user_namespace *user_ns, struct pid_namespace *old_ns)
{
if (!(flags & CLONE_NEWPID))
return get_pid_ns(old_ns);
if (task_active_pid_ns(current) != old_ns)
return ERR_PTR(-EINVAL);
return create_pid_namespace(user_ns, old_ns);
}
这个重新定义实现了什么?为什么要完成?感谢您的帮助!
备注
该问题省略了从中提取源代码的源代码存储库中的相关信息,因此以下答案不适用于这些文件中的源代码。看起来最多两个定义之一是根据构建选项选择的,因此它们不会在同一个构建中同时使用。
关于 static inline
的旧答案
header中的版本标记为static inline
。在此声明中,static
导致名称 copy_pid_ns
具有 内部链接 ,这意味着此处的定义将仅适用于当前对 copy_pid_ns
的使用翻译单元(正在编译的源文件,包括它包含的文件)。 inline
的想法是这个函数这么小,如果在调用它的地方,编译器只是为它编写代码代替调用而不是实际使用子程序调用指令就好了功能。 (从技术上讲,inline
只是在这方面对编译器的建议。优秀的现代编译器大多自行决定要内联哪些函数。)
因为header中的定义是static inline
,其他翻译单元无法访问。其他源文件中的定义具有 外部链接 ,这意味着其他翻译单元中 copy_pid_ns
的任何使用都可以链接到它(如果它们没有自己的私有 static
版本)。那些在其他翻译单元中的使用将不得不使用实际的子程序调用指令来调用此函数。
如果 copy_pid_ns
的所有使用都有可用的静态版本,则没有必要提供外部版本,但可能会提供一个,因为情况并非总是如此,或者只是作为安全后备。
这不是重新定义。实现此处意图的关键是存在第一个定义的 static
限定符,并了解如何使用 C 构建程序。此外,inline
很少在没有 static
的情况下使用,至少带有 C89 和 GNU 扩展,用于编译 Linux,但 inline
本身和外部本身在这里不起作用。
pid_namespace.h
中的 static inline
定义仅对包含该头文件的翻译单元(.c 文件)直接或间接可见。 static
限定符限制了这种可见性。如果您包含来自另一个翻译单元的 pid_namespace.h
,则该翻译单元将拥有自己的函数的另一个副本。每个目标文件都有自己的函数定义副本,仅供自己使用。此外,由于 inline
限定符,该函数甚至可能不存在,因为编译器 is free to "inline" it.
第二个定义没有 static
限定符,因此可以被本身不包含它的其他翻译单元使用。 C 编译器从它们各自的翻译单元编译目标文件,链接器将这些目标文件链接在一起并生成一个程序映像,其中对所述定义的引用被正确解析。编译器不关心是否有多个翻译单元嵌入了相同的函数——对它来说,它们只是需要一个一个编译的目标文件。但是链接器将中止,因为它并非设计为必须在其输入中的多个定义之间进行选择。
在实践中,有很多方法可以使用它。前者的定义可能是内核模式(特权内核代码)中使用的简短版本,而后者则用于用户模式,例如作为处理系统调用的结果。
一般来说,允许多次定义过程,只要链接器最多有一个对象可用(以防它需要解析对函数的引用,如函数调用)。您可以随心所欲地处理 .c 和 .h 文件,只要符合该要求即可。如果您还知道如何使用 static
和 inline
限定符,则可以添加外部不可见的定义 (static
) 并受益于编译器能够内联它们。
先看函数前面的限定符:
- static inline... -> static 将仅在包含的源文件(*.c、*.cpp)中解析
与私有限定符一样,inline 将直接替换此函数,而不是将其作为引用访问
- 没有限定符 -> 更像是一个 public 外部函数。如果您将函数的用法定义为 extern 您应该编译包含函数实现的 (*.c) 文件,然后链接器将解析您对该函数的调用作为参考。
这两个定义对应两个不兼容的配置:
include/linux/pid_namespace.h#L76
header 中 copy_pid_ns
的定义仅使用 CONFIG_PID_NS
选项进行解析 已禁用 (请参阅 header 中的第 68 行)。
文件kernel/pid_namespace.c
仅使用CONFIG_PID_NS
选项enabled编译(可从kernel/Makefile
找到) .对于该配置,header 仅包含第 62 行的函数声明。
header 文件两次声明某个函数的情况对于 Linux 内核源代码来说是很自然的:
一个声明(没有定义)对应于启用的某些功能。在该配置中,函数在某些源文件中 定义,仅在启用功能的情况下编译。
另一个声明是static inline
函数的定义,对应于禁用的功能。
我正在通读 Linux v3.19 的 PID 名称空间实现,在 pid_namespace.h
中定义了一些函数,这些函数在 pid_namespace.c
中重新定义。例如,在pid_namespace.h
中有如下定义:
static inline struct pid_namespace *copy_pid_ns(unsigned long flags,
struct user_namespace *user_ns, struct pid_namespace *ns)
{
if (flags & CLONE_NEWPID)
ns = ERR_PTR(-EINVAL);
return ns;
}
然后在 pid_namespace.c
中有第二个 copy_pid_ns
定义:
struct pid_namespace *copy_pid_ns(unsigned long flags,
struct user_namespace *user_ns, struct pid_namespace *old_ns)
{
if (!(flags & CLONE_NEWPID))
return get_pid_ns(old_ns);
if (task_active_pid_ns(current) != old_ns)
return ERR_PTR(-EINVAL);
return create_pid_namespace(user_ns, old_ns);
}
这个重新定义实现了什么?为什么要完成?感谢您的帮助!
备注
该问题省略了从中提取源代码的源代码存储库中的相关信息,因此以下答案不适用于这些文件中的源代码。看起来最多两个定义之一是根据构建选项选择的,因此它们不会在同一个构建中同时使用。
关于 static inline
的旧答案
header中的版本标记为static inline
。在此声明中,static
导致名称 copy_pid_ns
具有 内部链接 ,这意味着此处的定义将仅适用于当前对 copy_pid_ns
的使用翻译单元(正在编译的源文件,包括它包含的文件)。 inline
的想法是这个函数这么小,如果在调用它的地方,编译器只是为它编写代码代替调用而不是实际使用子程序调用指令就好了功能。 (从技术上讲,inline
只是在这方面对编译器的建议。优秀的现代编译器大多自行决定要内联哪些函数。)
因为header中的定义是static inline
,其他翻译单元无法访问。其他源文件中的定义具有 外部链接 ,这意味着其他翻译单元中 copy_pid_ns
的任何使用都可以链接到它(如果它们没有自己的私有 static
版本)。那些在其他翻译单元中的使用将不得不使用实际的子程序调用指令来调用此函数。
如果 copy_pid_ns
的所有使用都有可用的静态版本,则没有必要提供外部版本,但可能会提供一个,因为情况并非总是如此,或者只是作为安全后备。
这不是重新定义。实现此处意图的关键是存在第一个定义的 static
限定符,并了解如何使用 C 构建程序。此外,inline
很少在没有 static
的情况下使用,至少带有 C89 和 GNU 扩展,用于编译 Linux,但 inline
本身和外部本身在这里不起作用。
pid_namespace.h
中的 static inline
定义仅对包含该头文件的翻译单元(.c 文件)直接或间接可见。 static
限定符限制了这种可见性。如果您包含来自另一个翻译单元的 pid_namespace.h
,则该翻译单元将拥有自己的函数的另一个副本。每个目标文件都有自己的函数定义副本,仅供自己使用。此外,由于 inline
限定符,该函数甚至可能不存在,因为编译器 is free to "inline" it.
第二个定义没有 static
限定符,因此可以被本身不包含它的其他翻译单元使用。 C 编译器从它们各自的翻译单元编译目标文件,链接器将这些目标文件链接在一起并生成一个程序映像,其中对所述定义的引用被正确解析。编译器不关心是否有多个翻译单元嵌入了相同的函数——对它来说,它们只是需要一个一个编译的目标文件。但是链接器将中止,因为它并非设计为必须在其输入中的多个定义之间进行选择。
在实践中,有很多方法可以使用它。前者的定义可能是内核模式(特权内核代码)中使用的简短版本,而后者则用于用户模式,例如作为处理系统调用的结果。
一般来说,允许多次定义过程,只要链接器最多有一个对象可用(以防它需要解析对函数的引用,如函数调用)。您可以随心所欲地处理 .c 和 .h 文件,只要符合该要求即可。如果您还知道如何使用 static
和 inline
限定符,则可以添加外部不可见的定义 (static
) 并受益于编译器能够内联它们。
先看函数前面的限定符:
- static inline... -> static 将仅在包含的源文件(*.c、*.cpp)中解析 与私有限定符一样,inline 将直接替换此函数,而不是将其作为引用访问
- 没有限定符 -> 更像是一个 public 外部函数。如果您将函数的用法定义为 extern 您应该编译包含函数实现的 (*.c) 文件,然后链接器将解析您对该函数的调用作为参考。
这两个定义对应两个不兼容的配置:
include/linux/pid_namespace.h#L76
header 中copy_pid_ns
的定义仅使用CONFIG_PID_NS
选项进行解析 已禁用 (请参阅 header 中的第 68 行)。文件
kernel/pid_namespace.c
仅使用CONFIG_PID_NS
选项enabled编译(可从kernel/Makefile
找到) .对于该配置,header 仅包含第 62 行的函数声明。
header 文件两次声明某个函数的情况对于 Linux 内核源代码来说是很自然的:
一个声明(没有定义)对应于启用的某些功能。在该配置中,函数在某些源文件中 定义,仅在启用功能的情况下编译。
另一个声明是
static inline
函数的定义,对应于禁用的功能。