Подтвердить что ты не робот

Почему исполняемый файл такой большой? (Почему не удаляется мертвый код?)

Компиляция и связывание этого файла приводит к выполнению 1-KiB:

#pragma comment(linker, "/Entry:mainCRTStartup") // No CRT code (reduce size)
#pragma comment(linker, "/Subsystem:Console")    // Needed if avoiding CRT

#define STRINGIFIER(x)    func##x
#define STRINGIFY(x)      STRINGIFIER(x)
#define G   int STRINGIFY(__COUNTER__)(void) { return __COUNTER__; }

int mainCRTStartup(void) { return 0; }  // Does nothing

#if 0
    // Every `G' generates a new, unused function
    G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G
    G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G
#endif

Когда вы меняете #if 0 на #if 1), размер вывода удваивается до 2 KiB.

Кажется, что это происходит со всеми версиями Visual С++ на сегодняшний день, хотя мои параметры командной строки содержат все оптимизации, о которых я мог подумать:

/Ox /MD /link /fixed /OPT:ICF /OPT:REF

и, в частности, я не содержал никакой отладочной информации.

Кто-нибудь знает, почему /OPT:REF не заставляет компоновщик удалять неиспользуемые функции?

4b9b3361

Ответ 1

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

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

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

Из документации Microsoft для /OPT:

/OPT: REF

LINK удаляет неупорядоченные упакованные функции по умолчанию. Объект содержит упакованные функции (COMDAT), если он был скомпилирован с параметром /Gy. Эта оптимизация называется транзитивным удалением COMDAT. Чтобы переопределить это значение по умолчанию и сохранить неподдерживаемые COMDAT в программе, укажите /OPT: NOREF. Вы можете использовать параметр /INCLUDE для переопределения удаления определенного символа.

Параметр /Gy позволяет связывать на уровне функций.

Для справки эта функция также существует в gcc:

-ffunction сечения
-fdata-sections

Поместите каждую функцию или элемент данных в свой раздел в выходной файл, если цель поддерживает произвольные разделы. Имя функции или имя элемента данных определяет имя разделов в выходном файле.

     

Используйте эти параметры в системах, где компоновщик может выполнять оптимизацию, чтобы улучшить локальность ссылки в пространстве команд. Большинство систем, использующих формат объекта ELF, и процессоры SPARC, работающие под управлением Solaris 2, имеют компоновщики с такими оптимизациями. AIX может иметь эти оптимизации в будущем.

     

Используйте эти параметры только тогда, когда есть значительные преимущества от этого. Когда вы укажете эти параметры, ассемблер и компоновщик создадут более крупные объекты и исполняемые файлы, а также будут медленнее. Вы не сможете использовать "gprof" во всех системах, если вы укажете эту опцию, и у вас могут возникнуть проблемы с отладкой, если вы укажете оба параметра и -g.

И опция компаньона в ld:

- gc-sections

Включить сбор мусора из неиспользуемых разделов ввода. Он игнорируется в отношении целей, которые не поддерживают этот параметр. Этот параметр несовместим с -r или -emit-relocs. Поведение по умолчанию (не выполнять эту сборку мусора) можно восстановить, указав в командной строке --no-gc-разделы.