G++ генерирует код для неиспользуемых шаблонных специализаций?
В небольшом коде сериализации для проекта, над которым я работаю, у меня есть тип, размер которого зависит от компилятора. Чтобы справиться с этим, я решил использовать шаблонную специализацию, которая прекрасно работает. Все решается во время компиляции. Код выглядит примерно так (не настоящий код, просто пример):
template <int size>
void
special_function()
{
std::cout << "Called without specialization: " << size << std::endl;
}
template <>
void
special_function<4>()
{
std::cout << "dword" << std::endl;
}
template <>
void
special_function<8>()
{
std::cout << "qword" << std::endl;
}
int
main()
{
special_function<sizeof(int)>();
return 0;
}
В моей 32-битной системе выполнение вышеуказанных программных выходов dword
, как и ожидалось. Но весь смысл делать это таким образом, а не просто делать if (sizeof(int) == 4) { ... } else if ...
в том, что я надеялся, что компилятор выдаст код только для соответствующей функции. поскольку special_function<4>
это единственный файл, вызываемый в этой программе, я ожидал, что он будет единственным, сгенерированным компилятором (в данном случае gcc 4.1.2, на Linux x86).
Но это не наблюдаемое поведение.
Хотя это действительно работает, код для каждой специализации шаблона генерируется, несмотря на то, что он никогда не использовался. Общее определение не генерируется, однако.
Я должен отметить, что это одношаговая компиляция, а не компиляция в промежуточные объектные файлы, за которыми следует ссылка. В этом случае было бы естественным отложить удаление мертвого кода до стадии компоновки, и я знаю, что компоновщики не всегда очень хороши в этом.
Кто-нибудь знает что происходит? Есть ли здесь какая-то тонкость специализации шаблонов? Господь знает дьявола в деталях с C++.
РЕДАКТИРОВАТЬ: так как это было упомянуто, это поведение происходит с -O3 и -Os.
РЕДАКТИРОВАТЬ 2: Роб ниже предложил поместить функции в анонимное пространство имен. Выполнение этого и компиляция с любым уровнем оптимизации действительно удаляет мертвый код, и это хорошо. Но мне было любопытно, поэтому я попытался сделать то же самое со следующей программой:
namespace {
void foo() { std::cout << "Foo!" << std::endl; }
void bar() { std::cout << "Bar!" << std::endl; }
}
int
main()
{
foo();
return 0;
}
Идея здесь состоит в том, чтобы увидеть, действительно ли решение Роба связано со специализацией шаблонов. Оказывается, что приведенный выше код, скомпилированный с оптимизацией, включил неиспользуемое определение bar()
из исполняемого файла. Таким образом, кажется, что, хотя его ответ решает мою непосредственную проблему, он не объясняет, почему неиспользуемые специализации шаблона вообще компилируются.
Кто-нибудь знает соответствующий фрагмент из стандарта, который бы объяснил это? Я всегда думал, что шаблоны были созданы только при использовании, но, возможно, это не так для полной специализации...
2 ответа
Шаблонные специализации в вашем примере - это функции с внешней связью. Компилятор не может знать, что они не будут вызваны из другого модуля перевода.
В моей системе Ubuntu g++ 4.7.2 размещение шаблонов в анонимном пространстве имен и компиляция -O3
или же -O4
предотвратил создание неиспользуемой функции.
Аналогично, объявление шаблона функции static
имел желаемый эффект.
Это особая проблема. Я немного разбирался в этом, и эта проблема не связана со специализацией шаблонов. Я предполагаю, что по умолчанию g ++ не удаляет неиспользуемые символы. Это имеет смысл на тот случай, если позже вы захотите связать свой вывод с другой программой.
Однако существуют параметры командной строки, которые можно использовать для удаления неиспользуемых символов. Для деталей, смотрите этот пост:
Как удалить неиспользуемые символы C/C++ с помощью GCC и ld?
но также увидеть здесь
Использование GCC для поиска недоступных функций ("мертвый код")
и здесь
Обнаружение мертвого кода в устаревшем проекте C/C++
Просто чтобы попробовать это, я изменил код следующим образом:
#include <iostream>
void junk_function() {
std::cout<<"test" << std::endl;
}
template <int size>
void special_function()
{
std::cout << "Called without specialization: " << size << std::endl;
}
template <>
void special_function<4>()
{
std::cout << "dword" << std::endl;
}
template <>
void special_function<8>()
{
std::cout << "qword" << std::endl;
}
int main()
{
special_function<sizeof(int)>();
return 0;
}
Затем сохранил этот код в sp.cpp. Первый,
g++ -Os sp.cpp -o sp
nm sp
и получил это (обратите внимание, я удалил кучу символов для удобства чтения):
0804879a T _Z13junk_functionv
080487b8 T _Z16special_functionILi4EEvv
080487f5 T _Z16special_functionILi8EEvv
Кажется, там есть два неиспользованных символа. Я также попробовал -O1, -O2, -O3 и получил то же самое.
Следующий:
g++ -Os -fdata-sections -ffunction-sections sp.cpp -o sp -Wl,--gc-sections
nm sp
и получил это:
0804875a T _Z16special_functionILi4EEvv
Вот и все. Похоже, вам просто нужно передать правильные аргументы, чтобы g ++ убрал неиспользуемые символы. На Mac, я думаю, у них есть опция -dead_strip, но я не знаю, почему она не работает в g ++ (даже если это упоминается на страницах man. Правда, я не углублялся в это, так что быть мелким шрифтом, который я пропустил).
Я думаю, что компоновщик Visual C++ по умолчанию удаляется при линковке, но я не тестировал. Может быть, кто-то еще может вмешаться.