Почему файлы заголовков 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.

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