Приделал к проекту очередную фишку. Было свободное время, т.к. коллега свою часть ещё не доделал. Дай, думаю, посмотрю, какие предупреждения выдаёт компилятор. Я человек простой -- предупреждения компилятора, как правило, игнорирую. Но знаю одного человека, который не начинает отладку, пока не избавится от всех предупреждений.
Так вот, он прав.
Короче говоря среди кучи declared but never used и incompatible pointer type (я слышу возглас из зала: "тебе пора переходить на Rust!") я обнаружил действительно серьёзную вещь. "returning pointer to local variable".
Эта штука была в участке кода, который я с удовольствием скопировал из интернета. Кто же тут лоханулся, я или автор кода? Конечно же, я.
Автор кода выделял динамический массив:
int a=new int[n];
Я же, поскольку адаптировал код под микроконтроллер, благоразумно решил отказаться от динамической памяти. Нет, в микроконтроллерах эта механика всё равно работает, но мне опытные товарищи советовали не использовать её. Кроме того, я был уверен, что данная функция не будет эксплуатироваться при размере массива больше трёх (это была математическая функция). Поэтому я заменил строку выше на такую:
int a[3];
И удалил строку с освобождением памяти. А что происходит с этим массивом -- не посмотрел. В общем, он возвращался из функции:
return a;
И если при динамическом выделении памяти всё было бы хорошо, то при локальном (автоматическом) выделении памяти всё становится плохо. Функция завершается, освобождает память на стеке, а вызывающая функция начинает обращаться к ничьей области памяти.
* * *
Коллега по этой ситуации предложил интересную аналогию: представь себе, что ты дома положил на стол сотовый телефон и пошёл на работу. А на работе у себя на столе начинаешь его искать в том же месте, но почему-то не находишь!
Так вот, он прав.
Короче говоря среди кучи declared but never used и incompatible pointer type (я слышу возглас из зала: "тебе пора переходить на Rust!") я обнаружил действительно серьёзную вещь. "returning pointer to local variable".
Эта штука была в участке кода, который я с удовольствием скопировал из интернета. Кто же тут лоханулся, я или автор кода? Конечно же, я.
Автор кода выделял динамический массив:
int a=new int[n];
Я же, поскольку адаптировал код под микроконтроллер, благоразумно решил отказаться от динамической памяти. Нет, в микроконтроллерах эта механика всё равно работает, но мне опытные товарищи советовали не использовать её. Кроме того, я был уверен, что данная функция не будет эксплуатироваться при размере массива больше трёх (это была математическая функция). Поэтому я заменил строку выше на такую:
int a[3];
И удалил строку с освобождением памяти. А что происходит с этим массивом -- не посмотрел. В общем, он возвращался из функции:
return a;
И если при динамическом выделении памяти всё было бы хорошо, то при локальном (автоматическом) выделении памяти всё становится плохо. Функция завершается, освобождает память на стеке, а вызывающая функция начинает обращаться к ничьей области памяти.
* * *
Коллега по этой ситуации предложил интересную аналогию: представь себе, что ты дома положил на стол сотовый телефон и пошёл на работу. А на работе у себя на столе начинаешь его искать в том же месте, но почему-то не находишь!
03.02.2018 в 15:47
Ещё с возникают проблемы с чужим кодом, который не охота сейчас разбирать; тогда приходится пользоваться всякими #pragma GCC diagnostic ignored "-Wunused-result" для подавления конкретного предупреждения в одном конкретном месте. Причём эти костыли нестандартны и потому не портабельны (но #pragma обрабатывается компилятором, так что можешь сделать макрос, который по-разному определяется для разных компиляторов).
Зато эта опция отлично защищает от ситуации, когда предупреждение возникает в каком-то редко пересобираемом кусочке кода. Без опции это легко прозевать (при первой сборке увидишь и проигнорируешь, а потом оно уже не появится), зато с опцией билд будет постоянно прерываться, пока не починишь все до единой проблемы.
-- Minoru
03.02.2018 в 15:49
-- Minoru
04.02.2018 в 01:02
>>Welcome to the club
А ты знаешь, что под тем человеком, который не начинает отлаживать, пока не избавится от предупреждений, я имел ввиду тебя?) Это правда.
>>ты же просматриваешь дифф изменений перед тем, как чекинить их?)
Не всегда. Когда я делаю только одну задачу, то перед коммитом я проверяю только, что проект собирается без ошибок. Вот если я делал одну задачу, а тут ещё надо было паре человек дописать две тестовые строчки, слегка меняющие работу программы, но которые потом надо удалить, то ДА, я просматриваю дифф изменений, чтобы в коммит не попали эти тестовые строчки.
>>Зато эта опция отлично защищает от ситуации, когда предупреждение возникает в каком-то редко пересобираемом кусочке кода.
Да, вот это тема. Об этом я не думал, спасибо! Возможно, я перейду на твою систему.
04.02.2018 в 12:54
Проскакивала такая мысль, но я её сразу прогнал, решив, что это говорит моя самовлюблённость. Не может такого быть, чтобы я единственный в твоём круге общения так щепетильно относился к предупреждениям компилятора!
А возглас из зала, по твоей задумке, кто должен был выкрикнуть?
Вот если я делал одну задачу, а тут ещё надо было паре человек дописать две тестовые строчки […]
…то ты сразу вспоминал, что пора окончательно переходить на Git!
«git stash», по сути, просто создаёт отдельную ветку, коммитит в неё текущие изменения и делает чекаут предыдущего коммита. «git stash pop", соответственно, чекаутит последний созданный стэш и удаляет его ветку (т.е. следующий вызов «git stash pop» будет пытаться восстановить не последний, а предпоследний стэш).
В Subversion можно то же самое сделать как вручную, так и с помощью скриптов; см. пост на StackOverflow и скрипт на GitHub. Но я эти вещи, естественно, не использовал и ручаться за них не могу.
-- Minoru