Почему заголовочные файлы C не увеличивают размер двоичного файла? - PullRequest
10 голосов
/ 07 августа 2009

Я написал следующую программу на 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 d записал заголовочный файл 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 d заполнил файл stdio.h и снова скомпилировал

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

Как ни странно, размер исполняемых файлов остался прежним.

Ответы [ 7 ]

18 голосов
/ 07 августа 2009

Включив iostream в ваш исходный файл, компилятор должен сгенерировать код для настройки и демонтировать стандартную библиотеку ввода-вывода C ++. Вы можете увидеть это, посмотрев на вывод из 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 "- это код в сегменте" текст "." 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 не требует какой-либо дополнительной инициализации сверх того, что уже происходит в C динамическая библиотека. Вы можете увидеть это, посмотрев на вывод nm, который идентичен.

В общем, попытка интуитивно понять информацию об объеме кода, сгенерированного конкретным исходным файлом на основе размера исполняемого файла, не будет иметь смысла; слишком многое может измениться, и простые вещи, такие как расположение исходного файла, могут изменить двоичный файл, если компилятор содержит отладочную информацию.

Вы также можете найти objdump полезным для просмотра содержимого ваших исполняемых файлов.

9 голосов
/ 07 августа 2009

Заголовочные файлы обычно являются декларациями и не приводят к генерации машинного кода. Компоновщик достаточно умен, чтобы не извлекать неиспользуемые функции из CRT, поэтому простое включение stdio.h без использования каких-либо его функций не приведет к большему количеству кода в вашем исполняемом файле.

РЕДАКТИРОВАТЬ: они могут включать встроенные функции, классы и т. Д., Которые включают в себя код, но они не должны приводить к увеличению вашего исполняемого размера, пока они фактически не используются.

7 голосов
/ 07 августа 2009

iostream включает в себя код. stdio.h нет.

В частности, следующие определения в iostream (их больше, чем перечислено, и зависит от компилятора), ссылаются на объекты, созданные в стандартной библиотеке, которые затем связываются с вашим кодом:

extern istream &cin;
extern ostream &cout;
extern ostream &cerr;
extern ostream &clog;
3 голосов
/ 07 августа 2009

В iostream есть некоторые статические инициализации, в то время как в stdio.h есть только функции и их определения. Поэтому включение iostream приведет к созданию исполняемого файла большего размера.

2 голосов
/ 07 августа 2009

Как правило, заголовочные файлы содержат только информацию для компилятора, а не фактический код. Например:

struct foo {
  int x;
};

Подобные определения структуры часто появляются в заголовках, но на самом деле они не вызывают генерацию кода, поскольку они дают компилятору только информацию о том, как обрабатывать 'foo', если он увидит это позже. Если он не видит foo, информация теряется при завершении компиляции.

Фактически, наличие всего, что генерирует , генерирует код, как правило, приводит к ошибке. Например:

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

Если он находится в заголовке и включается в два файла .c, функция foo() будет сгенерирована дважды . Это приведет к ошибке по ссылке. Эту ошибку можно избежать, если у вас есть для этого веские основания, но, как правило, генерацию кода в заголовках по возможности избегают.

Обратите внимание, что одним исключением здесь являются встроенные члены в C ++. Например:

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

Технически говоря bar () генерируется в каждом файле, который содержит этот код. Компилятор выполняет различные приемы, чтобы избежать ошибки (слабая привязка и т. Д.). Это действительно может увеличить размер исполняемого файла, и это, вероятно, то, что вы видели с <iostream>.

1 голос
/ 08 августа 2009

Заголовок <iostream> содержит несколько объектов: 'std :: cin , 'std::cout, std::cerr и std::clog. Это экземпляры классов, которые имеют нетривиальные конструкторы и деструкторы. Это код и должен быть связан. Это то, что увеличивает размер исполняемого файла.

AFAIK, <cstdio> не поставляется с кодом, поэтому увеличение размера исполняемого файла не происходит.

0 голосов
/ 07 августа 2009

В файле iostream объявлены некоторые глобальные объекты:

std :: cout, std :: cerr, std :: cin, которые имеют тип ostream.

Затем компилятор перенесет этот класс и скомпилирует его прямо в ваш окончательный двоичный файл, добавив так много к его размеру.

...