在一个文件中推送所有内容的优缺点是什么:
void function(void) {
code...
}
为函数创建一个全新的文件:
#include <stdio.h>
#include "header.h"
一个或另一个更快吗?更轻巧?我的情况是速度是必要的,便携性是必须的。
我可以添加这一切都是基于C。
答案 0 :(得分:3)
如果您关心速度,首先应该编写正确的程序,关注有效的算法(阅读Introduction to Algorithms),基准&amp; profile它(可能使用gprof和/或oprofile),并将您的工作重点放在对性能至关重要的源代码的几个百分点上。
你最好定义这些小的关键函数,共同包含头文件作为static inline
函数。然后编译器可以inline每次调用它们(并且它需要访问函数的定义以进行内联)。
通常,小内联函数通常运行得更快,因为编译后的机器代码中没有调用开销;有时,它可能会稍慢一些,因为内联会增加机器代码的大小,这对CPU cache效率有害(阅读locality of reference)。此外,具有许多static inline
函数的头文件需要更多时间进行编译。
作为一个具体示例,我的Linux系统有一个标题/usr/include/glib-2.0/glib/gstring.h
(来自GTK中的Glib)
/* -- optimize g_string_append_c --- */
#ifdef G_CAN_INLINE
static inline GString*
g_string_append_c_inline (GString *gstring,
gchar c)
{
if (gstring->len + 1 < gstring->allocated_len)
{
gstring->str[gstring->len++] = c;
gstring->str[gstring->len] = 0;
}
else
g_string_insert_c (gstring, -1, c);
return gstring;
}
#define g_string_append_c(gstr,c) g_string_append_c_inline (gstr, c)
#endif /* G_CAN_INLINE */
G_CAN_INLINE
预处理程序标志已由某些先前包含的头文件启用。
这是inline
函数的一个很好的例子:它很短(十几行),它会快速运行自己的代码(不包括调用g_string_insert_c
的时间),所以它是值得定义为static inline
。
不值得定义为inline
一个短暂的功能,它本身就是一个重要的时间。例如,没有必要内联矩阵乘法(调用开销是无关紧要的,例如,制作100x100或8x8矩阵乘法的时间)。因此,请仔细选择您想要的功能inline
。
您应该信任编译器,并启用其optimizations(特别是在进行基准测试或分析时)。对于GCC,这意味着要使用gcc -O3 -mcpu=native
进行编译(我还建议使用-Wall -Wextra
来获取有用的警告)。您可以通过编译并使用gcc -flto -O3 -mcpu=native
链接来使用链接时优化
答案 1 :(得分:1)
您需要清楚头文件,翻译单元和单独编译的概念。
#include
指令只是在包含点插入包含文件的内容,就好像它是一个文件一样,所以在这种意义上将内容放入标题文件没有语义或性能差异“在一个文件中推送所有内容”。
重点是,不应该使用头文件或者它们的用途;你将很快遇到链接器错误和/或代码膨胀除了最琐碎的程序之外的任何东西。头文件通常应包含仅声明性代码而不是最终代码。例如,查看标准头文件内部 - 你会发现没有函数定义,只有声明(可能有一些接口定义为宏,或者可能是因为C99,内联函数,但这是一个不同的问题)。
提供的头文件是一种支持在单独的翻译单元中单独编译和链接代码的方法。翻译单元是源文件(在这种情况下为.c),在实际编译之前由预处理器扩展了所有#include
'ed和#define
'ed等内容。
当编译器构建翻译单元时,将会有未解析的链接到标题中声明的外部代码。这些声明是对编译器的承诺,即声明的形式的接口在别处定义,并且将由链接器解析。
多模块C程序源的常规形式(虽然几乎没有限制阻止你从非常规或愚蠢的事情)如下:
#include foobar.h
int main( void )
{
int x = foo() ;
bar( x ) ;
return 0 ;
}
#if !defined foobar_INCLUDE
#define foobar_INCLUDE
int foo( void ) ;
void bar( int x ) ;
#endif
请注意,此处使用预处理程序可防止多次声明多次声明,这可能会在具有嵌套包含的复杂代码库中发生。你的所有标题都应该有这样的“包含守卫” - 一些编译器支持#pragma once
做同样的事情,但它的可移植性较差。
#include "foobar.h"
int foo( void )
{
int x = 0 ;
// do something
return x ;
}
void bar( int x )
{
// do something
}
然后main.c和foobar.c(以及任何其他模块)被单独编译然后链接,链接器还解析对标准库或任何其他外部库提供的库接口的引用。在这个意义上,库只是以前单独编译的目标代码的集合。
现在也许很清楚,回答你的问题,但重新提出它作为单独编译和链接的利弊,其好处是:
具体而言,单独的编译和链接在正常情况下对性能或代码大小没有任何影响。在某些情况下,当编译器无法同时查看所有代码时,可能会影响编译器优化的能力,但是如果根据高内聚的原则仔细分区您的代码, 最小耦合这种潜在的机会损失可能是微不足道的。此外,现代链接器能够执行一些跨模块优化,例如在任何情况下都不使用代码删除。
答案 2 :(得分:-1)
这不是一个“更快”的问题。当您具有要在许多其他地方或其他项目中使用的功能时,会自定义创建头文件。例如,如果你编写了一个函数来计算一个数字的阶乘,你想在其他程序中使用该函数(或者你发现你必须在其他程序中复制相同的代码)那么而不是在其他程序中编写函数,如果你把它放在头文件中会更方便。通常,头文件包含与某个主题相关的函数(如math.h
包含用于数学计算的函数,而不包含用于字符串处理的函数)。