14

我编写了以下 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

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

4

7 回答 7

21

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

将此与从源文件生成的函数进行比较,但不包括iostream

$ 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,因为 C 标准 I/O 库不需要在 C 动态库中已经发生的事情之外进行任何额外的初始化。您可以通过查看nm相同的输出来看到这一点。

但是,一般来说,试图根据可执行文件的大小来直观地了解特定源文件生成的代码量是没有意义的。有太多东西可以改变,如果编译器包含调试信息,源文件的位置等简单的事情可能会改变二进制文件。

您可能还会发现objdump在您的可执行文件的内容中四处寻找很有用。

于 2009-08-07T18:42:38.227 回答
9

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

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

于 2009-08-07T18:22:39.780 回答
7

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

更具体地说,iostream 中的以下定义(不止列出,并且因编译器而异)引用标准库中创建的对象,然后将其链接到您的代码中:

extern istream &cin;
extern ostream &cout;
extern ostream &cerr;
extern ostream &clog;
于 2009-08-07T18:21:28.473 回答
3

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

于 2009-08-07T18:24:32.990 回答
2

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

struct foo {
  int x;
};

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

事实上,任何生成代码的东西通常都会产生错误。例如:

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

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

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

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

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

于 2009-08-07T18:23:54.957 回答
2

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

AFAIK,<cstdio>不附带代码,所以这就是可执行文件大小没有增加的原因。

于 2009-08-07T20:02:24.667 回答
0

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

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

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

于 2009-08-07T18:27:33.810 回答