Достался мне в наследство проект, в котором без упаковки структур никак. Но к счастью, многое в нем было уже реализовано и довольно сносно работало.
В частности, в нем было много объявлений структур вроде такой:
#pragma pack(1)
struct header
{
...
};
Про выравнивание полей структур я знал давно, но как-то жить мне это никогда не мешало, так что упаковывать структуры не приходилось. Если бы такая возможность была заложена в стандарт C++, то я бы о ней знал, а так -- нет.
Я сказал okay и, доверившись опыту прошлых разработчиков, принял на веру такой синтаксис.
Некоторое время все было хорошо, но в какой-то момент проект буквально посыпался.
Абсолютно непонятные ошибки вроде "записал в поле структуры одно, а при чтении получаешь другое". Особенно выносил мозг тот факт, что объект структуры был объявлен в глобальной области, и такое поведение программы не поддавалось объяснению.
Я сидел над этой ошибкой ровно неделю, перелопачивая немаленький проект, несколько раз откатывался назад на несколько недель, перебирал потенциальные причины проблемы, к концу недели я практически стал рвать волосы на голове, даже жж завел, чтобы было куда поплакаться).
За это время нашел и устранил ряд других серьезных ошибок, переписал многие хоть сколь-нибудь потенциально опасные участки кода, но все это не решало проблемы.
Коллеги склонялись к версии, что это ошибка компилятора. Я отказывался в это верить, но когнитивный диссонанс заставлял принять какое-нибудь объяснение происходящему.
Я остановился на версии "какой-то хитрой оптимизации компилятором многопоточности", хотя понимал, что это не так, хотя бы потому, что это невозможно принципиально по логическим причинам.
В выходные решил, то дальше так нельзя, и решил довольно радикально изменить некоторые элементы архитектуры, что и сделал в понедельник. Победа! Ошибка ушла, и проект стал развиваться дальше.
Но спустя месяц снова стали происходить аномалии с теми же симптомами, но на этот раз более явные: структура передавалась в функцию по ссылке, причем реализация функции находилась в другом модуле. Внутри функции половина полей, идущие друг за другом, начиная как раз с поля, имеющего не хороший с точки зрения выравнивания размер, была забита каким-то мусором, отличным от того, что было до входа в функцию. В этот момент мелькнула мысль, что придется потратить еще неделю чтобы выяснить в чем проблема. Неужели бага компилятора. Уныние. Забил и пошел домой.
Мытье посуды не требует умственного напряжения, так что голова освобождается для умных мыслей. Стою, мою посуду, и вдруг меня осеняет: в модуле, из которого функция вызывается, структура упакована, а в модуле с реализацией функции -- нет, это все объясняет, но как так?
И действительно, что за дурацкий синтаксис упаковки структур? Как компилятор будет интерпретировать pragma pack например перед объявлением функции, да и вообще, мне как-то с самого начала показалось странным использовать для упаковки структур нечто, начинающееся с шарпа/диеза -- ведь кажется есть __attribute__ для структур? И почему прежние разработчики писали так, и проект нормально работал?
Но больше ошибке скрываться было просто негде, и я полез в документацию на тему pragma pack. И вот оно: эта директива действует не только на следующую за ней структуру, но и вообще до конца файла, а так как в проекте она была написана в хедере, то вообще на весь транслируемый модуль. Таким образом, в одном модуле этот хедер подключался, а в другом нет (либо подключался выше объявления структуры):
модуль1.cpp:
#include "header.h" // объявление структуры header
// ...
header h;
// ...
func( h ); // реализация func -- в модуль2.cpp
модуль2.cpp:
#include "pragma.h" // хедер, содержащий "#pragma pack(1)"
#include "header.h" // объявление структуры header
// ...
void func( header& ) { ... }
Правильным решением (с использованием pragma pack) является такое:
#pragma pack(push, 1)
struct header
{
...
};
#pragma pack(pop)
В начале мы сохраняем (push) текущее значение опции упаковывать/не_упаковывать, затем включаем упаковку (1), и в конце восстанавливаем старое значение (pop).
Подобная ситуация описана здесь, но там все еще хуже: pragma pack(1) было написано в хедере сторонней библиотеки.
Вот так я исправил ошибку, жившую в проекте по меньшей мере год, и съевшую немало мозга мне и наверняка предыдущему разработчику.
Но для меня, как для начинающего разработчика, особенно важно не только фактическое исправление ошибки, но и выводы, которые можно сделать по результатам этой ситуации.
Первое. Не верь ничему и никому, до тех пор, пока не прочтешь документацию. Авторитетные люди -- тоже люди, и могут ошибаться.
Второе. Ошибка компилятора -- это последнее что можно предполагать.
Третье. Причины непонятных ошибок могут носить глобальный характер, но проявляться только в особых ситуациях. При этом сама ошибка и место ее проявления скорее всего находятся в абсолютно казалось бы несвязанных частях программы.
Желаю всем Совершенного кода!
В частности, в нем было много объявлений структур вроде такой:
#pragma pack(1)
struct header
{
...
};
Про выравнивание полей структур я знал давно, но как-то жить мне это никогда не мешало, так что упаковывать структуры не приходилось. Если бы такая возможность была заложена в стандарт C++, то я бы о ней знал, а так -- нет.
Я сказал okay и, доверившись опыту прошлых разработчиков, принял на веру такой синтаксис.
Некоторое время все было хорошо, но в какой-то момент проект буквально посыпался.
Абсолютно непонятные ошибки вроде "записал в поле структуры одно, а при чтении получаешь другое". Особенно выносил мозг тот факт, что объект структуры был объявлен в глобальной области, и такое поведение программы не поддавалось объяснению.
Я сидел над этой ошибкой ровно неделю, перелопачивая немаленький проект, несколько раз откатывался назад на несколько недель, перебирал потенциальные причины проблемы, к концу недели я практически стал рвать волосы на голове, даже жж завел, чтобы было куда поплакаться).
За это время нашел и устранил ряд других серьезных ошибок, переписал многие хоть сколь-нибудь потенциально опасные участки кода, но все это не решало проблемы.
Коллеги склонялись к версии, что это ошибка компилятора. Я отказывался в это верить, но когнитивный диссонанс заставлял принять какое-нибудь объяснение происходящему.
Я остановился на версии "какой-то хитрой оптимизации компилятором многопоточности", хотя понимал, что это не так, хотя бы потому, что это невозможно принципиально по логическим причинам.
В выходные решил, то дальше так нельзя, и решил довольно радикально изменить некоторые элементы архитектуры, что и сделал в понедельник. Победа! Ошибка ушла, и проект стал развиваться дальше.
Но спустя месяц снова стали происходить аномалии с теми же симптомами, но на этот раз более явные: структура передавалась в функцию по ссылке, причем реализация функции находилась в другом модуле. Внутри функции половина полей, идущие друг за другом, начиная как раз с поля, имеющего не хороший с точки зрения выравнивания размер, была забита каким-то мусором, отличным от того, что было до входа в функцию. В этот момент мелькнула мысль, что придется потратить еще неделю чтобы выяснить в чем проблема. Неужели бага компилятора. Уныние. Забил и пошел домой.
Мытье посуды не требует умственного напряжения, так что голова освобождается для умных мыслей. Стою, мою посуду, и вдруг меня осеняет: в модуле, из которого функция вызывается, структура упакована, а в модуле с реализацией функции -- нет, это все объясняет, но как так?
И действительно, что за дурацкий синтаксис упаковки структур? Как компилятор будет интерпретировать pragma pack например перед объявлением функции, да и вообще, мне как-то с самого начала показалось странным использовать для упаковки структур нечто, начинающееся с шарпа/диеза -- ведь кажется есть __attribute__ для структур? И почему прежние разработчики писали так, и проект нормально работал?
Но больше ошибке скрываться было просто негде, и я полез в документацию на тему pragma pack. И вот оно: эта директива действует не только на следующую за ней структуру, но и вообще до конца файла, а так как в проекте она была написана в хедере, то вообще на весь транслируемый модуль. Таким образом, в одном модуле этот хедер подключался, а в другом нет (либо подключался выше объявления структуры):
модуль1.cpp:
#include "header.h" // объявление структуры header
// ...
header h;
// ...
func( h ); // реализация func -- в модуль2.cpp
модуль2.cpp:
#include "pragma.h" // хедер, содержащий "#pragma pack(1)"
#include "header.h" // объявление структуры header
// ...
void func( header& ) { ... }
Правильным решением (с использованием pragma pack) является такое:
#pragma pack(push, 1)
struct header
{
...
};
#pragma pack(pop)
В начале мы сохраняем (push) текущее значение опции упаковывать/не_упаковывать, затем включаем упаковку (1), и в конце восстанавливаем старое значение (pop).
Подобная ситуация описана здесь, но там все еще хуже: pragma pack(1) было написано в хедере сторонней библиотеки.
Вот так я исправил ошибку, жившую в проекте по меньшей мере год, и съевшую немало мозга мне и наверняка предыдущему разработчику.
Но для меня, как для начинающего разработчика, особенно важно не только фактическое исправление ошибки, но и выводы, которые можно сделать по результатам этой ситуации.
Первое. Не верь ничему и никому, до тех пор, пока не прочтешь документацию. Авторитетные люди -- тоже люди, и могут ошибаться.
Второе. Ошибка компилятора -- это последнее что можно предполагать.
Третье. Причины непонятных ошибок могут носить глобальный характер, но проявляться только в особых ситуациях. При этом сама ошибка и место ее проявления скорее всего находятся в абсолютно казалось бы несвязанных частях программы.
Желаю всем Совершенного кода!
Комментариев нет:
Отправить комментарий