C - 头文件与函数

时间:2016-06-25 05:01:48

标签: c function header

在一个文件中推送所有内容的优缺点是什么:

void function(void) {
    code...
}

为函数创建一个全新的文件:

#include <stdio.h>
#include "header.h"

一个或另一个更快吗?更轻巧?我的情况是速度是必要的,便携性是必须的。

我可以添加这一切都是基于C。

3 个答案:

答案 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程序源的常规形式(虽然几乎没有限制阻止你从非常规或愚蠢的事情)如下:

的main.c

#include foobar.h

int main( void )
{
    int x = foo() ;
    bar( x ) ;
    return 0 ;
}

foobar.h中

#if !defined foobar_INCLUDE
#define foobar_INCLUDE

int foo( void ) ;
void bar( int x ) ;

#endif

请注意,此处使用预处理程序可防止多次声明多次声明,这可能会在具有嵌套包含的复杂代码库中发生。你的所有标题都应该有这样的“包含守卫” - 一些编译器支持#pragma once做同样的事情,但它的可移植性较差。

foobar.c但是

#include "foobar.h"

int foo( void )
{
    int x = 0 ;
    // do something
    return x ;
}

void bar( int x )
{
    // do something
}

然后main.c和foobar.c(以及任何其他模块)被单独编译然后链接,链接器还解析对标准库或任何其他外部库提供的库接口的引用。在这个意义上,只是以前单独编译的目标代码的集合。

现在也许很清楚,回答你的问题,但重新提出它作为单独编译和链接的利弊,其好处是:

  • 代码重用 - 您可以构建自己的有用例程库,这些例程可以在许多项目中重复使用,而不会出现错误的副本。粘贴。
  • 减少构建时间 - 在一个非平凡的应用程序上,单独的编译和链接将由构建管理器(如 make )或IDE(如 Ecipse 或<)进行管理em> Visual Studio ;这些工具执行增量构建仅编译那些已修改源或其中一个头依赖项的模块。这意味着您不是一直在编译所有代码,因此在调试和测试期间的周转速度要快得多。
  • 开发团队的可扩展性 - 如果您的所有代码都在一个文件中,那么让多个开发人员同时处理同一个项目几乎是不切实际的。如果你想在开源项目或职业生涯中与他人合作(当然这两者不一定互相排斥),你真的不能考虑一体化的方法。尤其是因为如果这是你的做法,你的开发人员不会认真对待玩具。

具体而言,单独的编译和链接在正常情况下对性能或代码大小没有任何影响。在某些情况下,当编译器无法同时查看所有代码时,可能会影响编译器优化的能力,但是如果根据高内聚的原则仔细分区您的代码, 最小耦合这种潜在的机会损失可能是微不足道的。此外,现代链接器能够执行一些跨模块优化,例如在任何情况下都不使用代码删除。

答案 2 :(得分:-1)

这不是一个“更快”的问题。当您具有要在许多其他地方或其他项目中使用的功能时,会自定义创建头文件。例如,如果你编写了一个函数来计算一个数字的阶乘,你想在其他程序中使用该函数(或者你发现你必须在其他程序中复制相同的代码)那么而不是在其他程序中编写函数,如果你把它放在头文件中会更方便。通常,头文件包含与某个主题相关的函数(如math.h包含用于数学计算的函数,而不包含用于字符串处理的函数)。