Почему файлы заголовков 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
Как ни странно, размер исполняемых файлов остался прежним.
Ответы
Ответ 1
Включив iostream
в исходный файл, компилятор должен сгенерировать код для настройки и срыва стандартной библиотеки ввода-вывода С++. Вы можете увидеть это, посмотрев на выходе из 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() [email protected]@GLIBCXX_3.4
U std::string::operator[](unsigned int) [email protected]@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
принимает имена функций С++, "искаженные" компилятором и генерирует более значимые имена. Первый столбец - это адрес, если функция включена в исполняемый файл. Второй столбец - это тип. "это код в сегменте" текст "." U "- это символы, соединенные из других мест, в этом случае - из общей библиотеки С++.)
Сравните это с функциями, сгенерированными из исходного файла без включения 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
полезный для поиска в содержимом ваших исполняемых файлов.
Ответ 2
Заголовочные файлы обычно являются просто объявлениями и не приводят к непосредственному генерированию машинного кода. Компилятор достаточно умен, чтобы не использовать неиспользуемые функции из ЭЛТ, поэтому просто включение stdio.h без использования каких-либо его функций не приведет к появлению большего количества кода в вашем исполняемом файле.
EDIT: они могут включать встроенные функции, классы и т.д., которые включают код, но это не должно приводить к увеличению размера исполняемого файла до тех пор, пока они фактически не будут использованы.
Ответ 3
iostream включает код. stdio.h не делает.
В частности, следующие определения в объектах iostream (больше, чем указано и изменяются в зависимости от компилятора), созданных в стандартной библиотеке, которые затем связаны с вашим кодом:
extern istream &cin;
extern ostream &cout;
extern ostream &cerr;
extern ostream &clog;
Ответ 4
В iostream есть несколько статических инициализаций, тогда как в stdio.h есть только функции и их определения. Поэтому включение iostream приведет к выполнению большего размера.
Ответ 5
Как правило, заголовочные файлы содержат только информацию для компилятора, а не фактический код. Например:
struct foo {
int x;
};
Такие определения структуры часто появляются в заголовках, но на самом деле они не приводят к генерации кода, поскольку они дают только информацию о компиляторе о том, как обрабатывать "foo", если она увидит ее позже. Если он не видит foo, информация теряется при завершении компиляции.
Фактически, наличие всего, что генерирует код, обычно приводит к ошибке. Например:
void foo() {
printf("bar!\n");
}
Если это находится в заголовке и входит в два .c файла, функция foo()
будет сгенерирована дважды. Это приведет к ошибке в ссылке. Можно избежать ошибки, если у вас есть веские основания для этого, но, как правило, вообще невозможно генерировать код в заголовках.
Обратите внимание, что одно исключение - это встроенные члены в С++. Например:
class Foo {
void bar() { /* ... */ }
};
С технической точки зрения bar() генерируется в каждом файле, который включает этот код. Компилятор выполняет различные трюки, чтобы избежать ошибки (слабая привязка и т.д.). Это может действительно увеличить размер исполняемого файла, и, вероятно, это то, что вы видели с помощью <iostream>
.
Ответ 6
Заголовок <iostream>
поставляется с несколькими объектами: 'std:: cin , 'std::cout
, std::cerr
и std::clog
. Это примеры классов, которые имеют нетривиальные конструкторы и деструкторы. Это код и должны быть связаны. Это увеличивает размер исполняемого файла.
AFAIK, <cstdio>
не поставляется с кодом, поэтому нет увеличения размера исполняемого файла.
Ответ 7
Файл iostream объявляет некоторые глобальные объекты:
std:: cout, std:: cerr, std:: cin, которые являются типом ostream.
Затем компилятор выведет этот класс и скомпилирует его прямо в ваш окончательный двоичный файл, добавив так много к его размеру.