为什么C头文件不会增加二进制文件的大小?

时间:2009-08-07 18:18:50

标签: c++ executable header-files

我编写了以下C ++程序

class MyClass {
public:
        int i;
        int j;
        MyClass() {};
};

int main(void)
{
        MyClass inst;
        inst.i = 1;
        inst.j = 2;
}

我编译了。

# g++ program.cpp
# ls -l a.out
-rwxr-xr-x  1 root  wheel  4837 Aug  7 20:50 a.out

然后,我#include了解源文件中的头文件iostream,然后我再次编译。

# g++ program.cpp
# ls -l a.out
-rwxr-xr-x  1 root  wheel  6505 Aug  7 20:54 a.out

正如预期的那样,文件大小增加了。

我还写了以下C程序

int main(void)
{
    int i = 1;
    int j = 2;
}

我编译了

# gcc program.c
# ls -l a.out
-rwxr-xr-x  1 root  wheel  4570 Aug  7 21:01 a.out

然后,我#include头文件stdio.h,我再次编译

# gcc program.c
# ls -l a.out
-rwxr-xr-x  1 root  wheel  4570 Aug  7 21:04 a.out

奇怪的是,可执行文件的大小保持不变。

7 个答案:

答案 0 :(得分:18)

通过在源文件中包含iostream,编译器需要生成代码来设置和拆除C ++标准I / O库。您可以通过查看nm的输出来看到这一点,$ nm --demangle test_with_iostream 08049914 d _DYNAMIC 08049a00 d _GLOBAL_OFFSET_TABLE_ 08048718 t global constructors keyed to main 0804883c R _IO_stdin_used w _Jv_RegisterClasses 080486d8 t __static_initialization_and_destruction_0(int, int) 08048748 W MyClass::MyClass() U std::string::size() const@@GLIBCXX_3.4 U std::string::operator[](unsigned int) const@@GLIBCXX_3.4 U std::ios_base::Init::Init()@@GLIBCXX_3.4 U std::ios_base::Init::~Init()@@GLIBCXX_3.4 080485cc t std::__verify_grouping(char const*, unsigned int, std::string const&) 0804874e W unsigned int const& std::min<unsigned int>(unsigned int const&, unsigned int const&) 08049a3c b std::__ioinit 08049904 d __CTOR_END__ ... (remaining output snipped) ... 显示目标文件上的符号(通常是函数):

--demangle

iostream通过编译器将C ++函数名称“修改”并生成更有意义的名称。如果函数包含在可执行文件中,则第一列是地址。第二列是类型。 “t”是“text”段中的代码。“U”是从其他地方链接的符号;在这种情况下,来自C ++共享库。)

将此与源文件生成的函数进行比较,不包括$ nm --demangle test_without_iostream 08049508 d _DYNAMIC 080495f4 d _GLOBAL_OFFSET_TABLE_ 080484ec R _IO_stdin_used w _Jv_RegisterClasses 0804841c W MyClass::MyClass() 080494f8 d __CTOR_END__ ... (remaining output snipped) ...

iostream

当您的源文件包含iostream时,编译器会生成多个不存在stdio.h的函数。

当源文件仅包含iostream时,生成的二进制文件类似于没有nm的测试,因为C标准I / O库不需要任何额外的初始化,超出已有的发生在C动态库中。您可以通过查看objdump输出来看到这一点,该输出完全相同。

但是,一般情况下,尝试根据可执行文件的大小直观地了解特定源文件生成的代码量并不会有意义;有太多可以改变的东西,如果编译器包含调试信息,那么像源文件的位置这样的简单事情可能会改变二进制文件。

您可能还会发现{{1}}对于浏览可执行文件的内容非常有用。

答案 1 :(得分:9)

头文件通常只是声明,不会直接导致生成机器代码。链接器足够智能,不会从CRT中提取未使用的函数,因此只包含stdio.h而不使用其任何函数不会在可执行文件中产生更多代码。

编辑:它们可以包含内联函数,类等,其中包含代码,但在实际使用之前,这些不应导致可执行文件大小的增加。

答案 2 :(得分:7)

iostream包含代码。 stdio.h没有。

更具体地说,iostream中的以下定义(除了列出并且由编译器改变之外)在标准库中创建的引用对象,然后链接到您的代码中:

extern istream &cin;
extern ostream &cout;
extern ostream &cerr;
extern ostream &clog;

答案 3 :(得分:3)

iostream中有一些静态初始化,而在stdio.h中只有函数和它们的定义。因此,包括iostream将产生更大的可执行文件。

答案 4 :(得分:2)

通常来说,头文件只包含编译器的信息,而不包含实际代码。例如:

struct foo {
  int x;
};

这样的结构定义经常出现在头文件中,但它们实际上并不会导致生成代码,因为它们只会给编译器提供有关如何处理'foo'的信息,如果它稍后再看到它。如果它没有看到foo,则在编译完成时信息会丢失。

事实上,拥有 生成代码的任何内容通常都会产生错误。例如:

void foo() {
  printf("bar!\n");
}

如果这是在标题中,并且包含在两个.c文件中,foo()函数将生成两次。这将导致链接错误。如果你有充分的理由这样做可以避免错误,但是如果可能的话,通常会避免在头文件中生成代码。

请注意,这里的一个例外是C ++中的内联成员。例如:

class Foo {
  void bar() { /* ... */ }
};

从技术上讲,bar()是在包含此代码的每个文件中生成的。编译器会做各种技巧来避免错误(弱绑定等)。这可能确实增加了可执行文件的大小,可能与您在<iostream>中看到的一样。

答案 5 :(得分:1)

<iostream>标头附带了几个对象,'std :: cin , 'std::coutstd::cerrstd::clog。这些是具有非平凡构造函数和析构函数的类的实例。这些是代码,必须链接。这就是增加可执行文件大小的原因。

AFAIK,<cstdio>没有代码,所以这就是为什么没有增加可执行文件的大小。

答案 6 :(得分:0)

iostream文件声明了一些全局对象:

std :: cout,std :: cerr,std :: cin是ostream类型。

然后编译器将带来该类并将其编译到最终的二进制文件中,增加了它的大小。