- PVSM.RU - https://www.pvsm.ru -
Представим, что у вас идеальный проект. Таски пилятся, компилятор компилирует, статические анализаторы анализируют, релизы релизятся. В какой‑то момент вы принимаете волевое решение открыть древний файл, в который никто не залезал уже много лет, и видите, что он в кодировке Windows-1251. При том, что весь проект уже давно перешёл на UTF-8. «Непорядок!» — думаете вы, и лёгким движением руки меняете кодировку. На следующий день на вашем тестовом сервере случается локальный апокалипсис. Думаете, такого не может быть? Тогда предлагаю это обсудить.
«Так исторически сложилось» — легендарная фраза. Универсальный отбойник для джунов и предвестник беды для опытных программистов. В нашем проекте это причина, по которой значительная часть файлов с исходным кодом находится в кодировке Windows-1251.
В какой‑то момент нам надоели проблемы с битыми символами, Doxygen'ом, да и вообще, как бы 2024 год на дворе. Возникла необходимость конвертировать файлы в UTF-8. А чтобы разным утилитам было проще автоматически определять кодировку, решили при конвертации добавлять BOM‑заголовок. Создали таску, и прогресс пошёл: таска выполнялась, файлы конвертировались. Казалось бы, антибугурт. Но всё не так просто.
В это же время другой программист выполнял другую таску. Задача была простая: нужно было немного отредактировать код в одном заголовочном файле. Программист решил помочь своим коллегам, немного сократив им фронт работ. Для этого он перевёл затронутый заголовочный файл в кодировку UTF-8 с BOM и закоммитил. Последствия «небольшого» фикса настигли компанию уже на следующий день: сборка и все ночные тесты были в ауте, а тимлид был в бешенстве. Начали разбираться.
При просмотре выхлопа компилятора было обнаружено множество сообщений о нарушении one definition rule (ODR) [1]. Складывалось впечатление, что кто-то забыл добавить в заголовочный файл #pragma once. Стали искать по коммитам стартовую точку, с которой начались проблемы, и не нашли. Все правки были достаточно простыми и не добавляли новых заголовочных файлов. Тем не менее спустя некоторое время подозрение пало на коммит с изменением кодировки заголовочного файла. Подозрение превратилось в уверенность, когда мы сделали минимальный воспроизводимый пример [2].
В этом примере простейший проект всего из пары файлов. Файл functions.h — предкомпилированный заголовочный файл [3], содержащий объявление класса и функции. Для защиты от двойного включения он содержит #pragma once. Файлы functions.cpp и main.cpp — это юниты трансляции, включающие предкомпилированный заголовочный файл.
Казалось бы, простейший код. Ошибок нет. Но проект не компилируется. Если попробовать собрать проект при помощи GCC (проверял на версиях 12.2.0 и 13.2.0), то вы увидите сообщения о нарушении ODR.
Но стоит вам лишь поменять кодировку файла functions.h на UTF-8 без BOM-заголовка, как все ошибки компиляции пропадут. Вот так GCC показал вам фокус с исчезновением. Жаль только, что исчезла #pragma once из вашего проекта.
Поискав похожие репорты в баг-трекере, я нашёл вот такой [4]. В нём даже есть свежее сообщение с патчем, который, возможно, пофиксит этот баг. Но пока всё глухо. Обновлений не было уже четыре года, а статус тикета по-прежнему unconfirmed. Что примечательно, тикет создан в 2013 году, и с тех пор баг так и не был исправлен.
Что это, если не бугурт?
Внимательные пользователи скажут мне: "Подожди, с 2013 года прошло 11 лет, почему ты тогда писал про 13 лет в заголовке?" Дело в том, что у тикета есть дубликат [5], и он старше оригинала на два года.
Пользователи GCC могут начать защищать свой компилятор, мол, у нас есть include guards [6], зачем нам ваша pragma? Развёл тут, понимаешь, дискуссии. Ну, если вы компилируете только под GCC и вам этого достаточно, тогда никаких вопросов к вам нет. Разве что писать макросы и #ifdef'ы менее удобно, чем pragma. Но вот если ваш продукт кроссплатформенный, собирающийся под разными компиляторами, тогда у вас беда и печаль: придётся либо пользоваться исключительно guard'ами, либо писать громоздкие #ifdef'ы специально для GCC.
В копилку проблем добавим third-party библиотеки, в которых может быть #pragma once. Причём с самим заголовочным файлом из third-party библиотеки может быть всё в порядке. Он даже может не иметь BOM-заголовка и находиться в нужной вам кодировке. И даже может не быть предкомпилированным. Ваш проект всё равно свалится, стоит лишь этому хедеру с pragma попасть в другой, который будет предкомпилированным. Например, в stdafx.h.
И хорошо будет, если у вас получится выяснить, из-за какого файла у вас падает сборка. Потому что очень часто в предкомпилированном заголовке находятся хедеры, которые включают хедеры, которые включают другие хедеры и т.д. И проблемный файл может находиться на самом дне этой иерархии. Даже если само наличие pragma не свалит вам сборку, то из-за множества включений хедера в юниты трансляции она может сильно замедлиться [7].
Для GCC pragma — это "больная тема". Помимо вышеописанного, у вас могут начаться проблемы с шаблонами [8], и бог знает с чем ещё. Огромное количество багов, связанных с pragma, и отсутствие фиксов для них породили культуру "отмены pragma". Сторонники компилятора GCC часто экстраполируют свой негативный опыт на другие компиляторы и призывают не использовать #pragma once вообще, мол, непредсказуемая вещь. Хотя, казалось бы, удобный инструмент, поддерживаемый во многих компиляторах.
Прошло уже как минимум 13 лет с появления бага в компиляторе и четыре года с публикации возможного фикса для него. GCC, не пора [9] ли?
Поддержка пользователей и своевременный фикс их проблем — это очень важно для выживания бизнеса. Мы в PVS-Studio уделяем много времени поддержке и стараемся оперативно выдавать фиксы пользователям, чтобы они могли пользоваться полным функционалом инструмента. Например, вы можете почитать другую мою статью, написанную в соавторстве с коллегой, о том, как мы разбирали 278 гигабайт логов [10].
Если вы хотите узнать про различные виды багов, и как они могут выглядеть в коде и на практике, приглашаю прочитать статьи моих коллег:
Улыбка сквозь баги [11]
Не исправил, а проработал принятие: как некоторые баги в играх стали фичами [12]
Проверка игрового движка qdEngine, часть первая: топ 10 предупреждений PVS-Studio [15]
От винта! Смотрим движок War Thunder и говорим с его создателями [16]
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Grigory Semenchev. An insect is sitting in your compiler and doesn't want to leave for 13 years [17].
Автор: Grigory Semenchev
Источник [18]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-3/392664
Ссылки в тексте:
[1] one definition rule (ODR): https://en.cppreference.com/w/cpp/language/definition#One_Definition_Rule
[2] минимальный воспроизводимый пример: https://github.com/ComeInRage/gcc_pragma_once_bug
[3] предкомпилированный заголовочный файл: https://pvs-studio.ru/ru/blog/posts/cpp/0265/
[4] такой: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56549
[5] дубликат: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=49837
[6] include guards: https://en.wikipedia.org/wiki/Include_guard
[7] замедлиться: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58770
[8] шаблонами: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64117#c0
[9] пора: https://pvs-studio.ru/ru/blog/posts/cpp/0900/
[10] разбирали 278 гигабайт логов: https://pvs-studio.ru/ru/blog/posts/cpp/1005/
[11] Улыбка сквозь баги: https://pvs-studio.ru/ru/blog/posts/cpp/1131/
[12] Не исправил, а проработал принятие: как некоторые баги в играх стали фичами: https://pvs-studio.ru/ru/blog/posts/1108/
[13] Баги, которые наделали немало шума: https://pvs-studio.ru/ru/blog/posts/1114/
[14] 30 лет DOOM: новый код — новые баги: https://pvs-studio.ru/ru/blog/posts/cpp/1087/
[15] Проверка игрового движка qdEngine, часть первая: топ 10 предупреждений PVS-Studio: https://pvs-studio.ru/ru/blog/posts/cpp/1119/
[16] От винта! Смотрим движок War Thunder и говорим с его создателями: https://pvs-studio.ru/ru/blog/posts/cpp/1098/
[17] An insect is sitting in your compiler and doesn't want to leave for 13 years: https://pvs-studio.com/en/blog/posts/cpp/1141/
[18] Источник: https://habr.com/ru/companies/pvs-studio/articles/827396/?utm_source=habrahabr&utm_medium=rss&utm_campaign=827396
Нажмите здесь для печати.