Your SlideShare is downloading. ×
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Как уменьшить вероятность ошибки на этапе написания кода. Заметка N1.

264

Published on

Я добрался до кода широко известного клиента мгновенных сообщений Miranda IM. Вместе с различными плагинами это достаточно большой проект, размер которого составляет около 950 тысяч строк кода на C и …

Я добрался до кода широко известного клиента мгновенных сообщений Miranda IM. Вместе с различными плагинами это достаточно большой проект, размер которого составляет около 950 тысяч строк кода на C и C++. И, как в любом солидном проекте с историей развития, в нем имеется немалое количество ошибок и опечаток.

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
264
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
1
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Как уменьшить вероятность ошибкина этапе написания кода. Заметка N1.Автор: Андрей КарповДата: 09.03.2011АннотацияЯ добрался до кода широко известного клиента мгновенных сообщений Miranda IM Вместе с IM.различными плагинами это достаточно большой проект, размер которого составляет около 950 итысяч строк кода на C и C++. И как в любом солидном проекте с историей развития, в нем имеется . И,немалое количество ошибок и опечаток.ВведениеРассматривая дефекты в различных приложениях, я заметил некоторые закономерности. И сейчасна примере дефектов, найденных в Miranda IM, я попробую сформулировать некоторыерекомендации, которые позволят избежать многих ошибок и опечаток ещ на этапе написания ещёкода.Для анализа Miranda IM я использовал анализатор PVS-Studio 4.14. Код проекта Miranda IM весьмакачественен, что подтверждается популярностью этой программы. Я и сам являюсьпользователем этого клиента и не имею к нему претензий в плане качества. Проект собирается в иентаVisual Studio с Warning Level 3 (/W3), а количество комментариев составляет 20% от всего текстапрограммы.1. Избегайте функции memset, memcpy, ZeroMemory и иманалогичныеМы начнем с ошибок, возникающих при использовании низкоуровневых функций работы спамятью такими, как memset memcpy, ZeroMemory и им подобных. memset,Рекомендую всячески избегать этих функций. Понятно, что не стоит буквально следовать моемусовету и заменять все эти функции на циклы. Но я видел такое огромное количество ошибок, ть функциисвязанных с использованием этих функций, что очень рекомендую быть аккуратными с ними ииспользовать только по необходимости. На мой взгляд есть только два случая, когдаиспользование этих функций опоправдано:1) Обработка больших массивов. То есть там, где оптимизированный алгоритм функции даствыигрыш по сравнению с обработкой элементов в простом цикле.
  • 2. 2) Обработка большого количества маленьких массивов. Также имеет смысл с точки зренияулучшения производительности.Во всех остальных случаях лучше попробовать обойтись без них. Например, я считаю эти функцииизлишними в такой программе, как Miranda. Никаких ресурсоёмких алгоритмов или огромныхмассивов в ней нет. Следовательно, использование функций memset/memcpy проистекает толькоиз удобства написания короткого кода. Однако, эта простота очень обманчива и, сэкономивнесколько секунд на написании кода, можно неделями вылавливать нечётко проявляющуюсяошибку порчи памяти. Рассмотрим несколько примеров кода, взятых из проекта Miranda IM.V512 A call of the memcpy function will lead to a buffer overflow or underflow. tabsrmm utils.cpp 1080typedef struct _textrangew{ CHARRANGE chrg; LPWSTR lpstrText;} TEXTRANGEW;const wchar_t* Utils::extractURLFromRichEdit(...){ ... ::CopyMemory(tr.lpstrText, L"mailto:", 7); ...}Копируем только кусок строки. Ошибка банальна до безобразия, но от этого не перестаёт бытьошибкой. Скорее всего, раньше здесь использовалась строка, состоящая из char. Затем перешлина Unicode строки, а поменять константу забыли.Если изначально использовать для копирования строки функции, которые для этого ипредназначаются, то такая ошибка просто не сможет возникнуть. Представим, что было написанотак:strncpy(tr.lpstrText, "mailto:", 7);Тогда при переходе к Unicode строкам, число 7 изменять не надо:wcsncpy(tr.lpstrText, L"mailto:", 7);Я не говорю, что это идеальный код. Но он уже намного лучше, чем использование CopyMemory.Рассмотрим другой пример.V568 Its odd that the argument of sizeof() operator is the & ImgIndex expression. clist_modernmodern_extraimage.cpp 302
  • 3. void ExtraImage_SetAllExtraIcons(HWND hwndList,HANDLE hContact){ ... char *(ImgIndex[64]); ... memset(&ImgIndex,0,sizeof(&ImgIndex)); ...}Здесь хотелось обнулить массив, состоящий из 64-указателей. Но вместо этого мы обнулим толькопервый элемент. Эта же ошибка, кстати, присутствует ещё раз в другом файле. Скажем спасибоCopy-Paste:V568 Its odd that the argument of sizeof() operator is the & ImgIndex expression. clist_mwextraimage.c 295Корректный вариант кода должен выглядеть следующим образом:memset(&ImgIndex,0,sizeof(ImgIndex));Кстати, взятие адреса у массива может только дополнительно запутать того, кто читает код. Здесьвзятие адреса не имеет никакого эффекта. И код может быть написан так:memset(ImgIndex,0,sizeof(ImgIndex));Следующий пример.V568 Its odd that the argument of sizeof() operator is the & rowOptTA expression. clist_modernmodern_rowtemplateopt.cpp 258static ROWCELL* rowOptTA[100];void rowOptAddContainer(HWND htree, HTREEITEM hti){ ... ZeroMemory(rowOptTA,sizeof(&rowOptTA)); ...}
  • 4. Снова, вместо вычисления размера массива, мы вычисляем размер указателя. Правильнымвыражением будет "sizeof(rowOptTA)". Для очистки массива я предлагаю следующий варианттакого кода:const size_t ArraySize = 100;static ROWCELL* rowOptTA[ArraySize];...std::fill(rowOptTA, rowOptTA + ArraySize, nullptr);Я уже привык, что подобные строки любят размножаться по программе копированием:V568 Its odd that the argument of sizeof() operator is the & rowOptTA expression. clist_modernmodern_rowtemplateopt.cpp 308V568 Its odd that the argument of sizeof() operator is the & rowOptTA expression. clist_modernmodern_rowtemplateopt.cpp 438Вы думаете, что это всё касательно низкоуровневой работы с масивами? Нет, не всё. Смотритедальше, бойтесь и бейте по рукам любителей написать memset.V512 A call of the memset function will lead to a buffer overflow or underflow. clist_modernmodern_image_array.cpp 59static BOOL ImageArray_Alloc(LP_IMAGE_ARRAY_DATA iad, int size){ ... memset(&iad->nodes[iad->nodes_allocated_size], (size_grow - iad->nodes_allocated_size) * sizeof(IMAGE_ARRAY_DATA_NODE), 0); ...}В этот раз размер копируемых данных вычислен корректно. Вот только перепутан местами второйи третий аргумент. Как следствие, мы заполняем 0 элементов. Корректный вариант:memset(&iad->nodes[iad->nodes_allocated_size], 0, (size_grow - iad->nodes_allocated_size) * sizeof(IMAGE_ARRAY_DATA_NODE));
  • 5. Как переписать этот фрагмент кода более изящно, я не знаю. Вернее, его нельзя переписатькрасиво, не затрагивая другие участки программы и структуры данных.Возникает вопрос, а как же обойтись без memset при работе с такими структурами, какOPENFILENAME:OPENFILENAME x;memset(&x, 0, sizeof(x));Очень просто. Создать обнуленную структуру можно так:OPENFILENAME x = { 0 };2. Внимательно следите, работаете вы со знаковым илибеззнаковым типомПроблема путаницы между signed и unsigned типами может на первый взгляд показатьсянадуманной. Но этот вопрос зря недооценивается программистами.В большинстве случаев, людям не нравится рассматривать предупреждения компилятора,связанные с тем, что переменная типа int сравнивается с переменной типа unsigned. Идействительно, чаще всего подобный код совершенно корректен. Программисты отключают такиепредупреждения или не обращают на них внимания. Или есть третий вариант, они вписываютявное приведение типа, чтобы заглушить предупреждение компилятора, не вдаваясь вподробности.Предлагаю больше так не делать и каждый раз анализировать ситуацию, когда знаковый типвстречается с беззнаковым. И, вообще, быть внимательным к тому, какой тип имеет выражениеили что возвращает функция. Теперь несколько примеров по рассматриваемой теме.V547 Expression wParam >= 0 is always true. Unsigned type value is always >= 0. clist_mwcluiframes.c 3140В коде программы имеется функция id2pos, которая возвращает значение -1 в случае ошибки. Сэтой функцией всё в порядке. В другом месте результат работы функции id2pos используетсяследующим образом:typedef UINT_PTR WPARAM;static int id2pos(int id);static int nFramescount=0;INT_PTR CLUIFrameSetFloat(WPARAM wParam,LPARAM lParam)
  • 6. { ... wParam=id2pos(wParam); if(wParam>=0&&(int)wParam<nFramescount) if (Frames[wParam].floating) ...}Проблема в том, что переменная wParam имеет беззнаковый тип. Следовательно, условиеwParam>=0 всегда истинно. Если функция id2pos вернет -1, то условие проверки недопустимыхзначений не сработает, и мы начнем использовать отрицательный индекс.Я почти уверен, что вначале был написан следующий код:if (wParam>=0 && wParam<nFramescount)Компилятор Visual C++ выдал предупреждение "warning C4018: < : signed/unsigned mismatch". Этопредупреждение как раз включено на Warning Level 3, с которым и собирается Miranda IM. И вэтот момент этому месту было уделено недостаточное внимание. Предупреждение былоподавлено явным приведением типа. Но ошибка не исчезла, а только спряталась. Корректнымвариантом должен был стать следующий код:if ((INT_PTR)wParam>=0 && (INT_PTR)wParam<nFramescount)Так что внимание и ещё раз внимание к подобным местам. В Miranda IM я насчитал 33 условия,которые из-за путаницы с signed/unsigned всегда истинны или всегда ложны.Продолжим. Следующий пример мне особенно нравится. А комментарий, просто прекрасен.V547 Expression nOldLength < 0 is always false. Unsigned type value is never < 0. IRC mstring.h 229void Append( PCXSTR pszSrc, int nLength ){ ... UINT nOldLength = GetLength(); if (nOldLength < 0) { // protects from underflow nOldLength = 0;
  • 7. } ...}Думаю, пояснений к этому коду не требуется.Конечно, в ошибках не всегда виноваты только программисты. Иногда свинью подкладывают иразработчики библиотек (в данном случае WinAPI).#define SRMSGSET_LIMITNAMESLEN_MIN 0static INT_PTR CALLBACK DlgProcTabsOptions(...){ ... limitLength = GetDlgItemInt(hwndDlg, IDC_LIMITNAMESLEN, NULL, TRUE) >= SRMSGSET_LIMITNAMESLEN_MIN ? GetDlgItemInt(hwndDlg, IDC_LIMITNAMESLEN, NULL, TRUE) : SRMSGSET_LIMITNAMESLEN_MIN; ...}Если не обращать внимания на излишне сложное выражение, то код выглядит корректно. Кстати,это вообще была одна строка. Для удобства я разбил её на несколько. Впрочем, форматированиекода сейчас мы затрагивать не будем.Проблема в том, что функция GetDlgItemInt() возвращает вовсе не int, как ожидал программист.Эта функция возвращает UINT. Вот её прототип из файла "WinUser.h":WINUSERAPIUINTWINAPIGetDlgItemInt( __in HWND hDlg, __in int nIDDlgItem, __out_opt BOOL *lpTranslated, __in BOOL bSigned);
  • 8. В результате PVS-Studio выдает сообщение:V547 Expression is always true. Unsigned type value is always >= 0. scriver msgoptions.c 458И это действительно так. Выражение "GetDlgItemInt(hwndDlg, IDC_LIMITNAMESLEN, NULL, TRUE) >=SRMSGSET_LIMITNAMESLEN_MIN" всегда истинно.Конкретно в данном случае, возможно, никакой ошибки и не будет. Но смысл моегопредупреждения, надеюсь, понятен. Внимательно смотрите, что возвращают вам функции.3. Избегайте большого количества вычислений в одной строкеКаждый программист знает и при обсуждениях ответственно заявляет, что надо писать простой ипонятный код. Но на практике иногда кажется, что он участвует в тайном конкурсе, кто напишетстроку похитрее, с использованием интересной конструкции языка или продемонстрируетумение жонглировать указателями.Чаще всего ошибки возникают там, где программист, с целью создать компактный код, собирает водну строку сразу несколько действий. При незначительном выигрыше в изящности онсущественно увеличивает риск опечатки или того, что не учтёт побочные действия. Рассмотримпример:V567 Undefined behavior. The s variable is modified while being used twice between sequence points.msn ezxml.c 371short ezxml_internal_dtd(ezxml_root_t root, char *s, size_t len){ ... while (*(n = ++s + strspn(s, EZXML_WS)) && *n != >) { ...}Здесь возникает неопределенное поведение (undefined behavior). Этот код может корректнофункционировать в течение долгого периода жизни программы. Но нет никакой гарантии, что онтакже будет вести себя после смены версии компилятора или ключей оптимизации. Компилятор вправе вначале вычислить ++s, а затем взывать функцию strspn(s, EZXML_WS). Или, наоборот,вначале он может вызвать функцию, а уже затем увеличить значение переменной s.Приведу другой пример, почему не стоит пытаться собрать всё в одну строку. В Miranda IMнекоторые ветви исполнения программы отключены/включены с помощью вставок вида && 0.Примеры:if ((1 || altDraw) && ...if (g_CluiData.bCurrentAlpha==GoalAlpha &&0)if(checkboxWidth && (subindex==-1 ||1)) {
  • 9. С приведенными сравнениями всё ясно и они хорошо заметны. А теперь представьте, что вывстречаете фрагмент показанный ниже. Код я отформатировал. В программе это ОДНА строка.V560 A part of conditional expression is always false: 0. clist_modern modern_clui.cpp 2979LRESULT CLUI::OnDrawItem( UINT msg, WPARAM wParam, LPARAM lParam ){ ... DrawState(dis->hDC,NULL,NULL,(LPARAM)hIcon,0, dis->rcItem.right+dis->rcItem.left- GetSystemMetrics(SM_CXSMICON))/2+dx, (dis->rcItem.bottom+dis->rcItem.top- GetSystemMetrics(SM_CYSMICON))/2+dx, 0,0, DST_ICON| (dis->itemState&ODS_INACTIVE&&FALSE?DSS_DISABLED:DSS_NORMAL)); ...}Если здесь нет ошибки, то всё равно непросто вспомнить и найти в этой строке слово FALSE. Вы егонашли? Согласитесь - непростая задача. А если это ошибка? Тогда вообще нет шансов найти её,просто просматривая код. Подобные выражения очень полезно выносить отдельно. Пример:UINT uFlags = DST_ICON;uFlags |= dis->itemState & ODS_INACTIVE && FALSE ? DSS_DISABLED : DSS_NORMAL;А я сам, пожалуй, написал бы это длинным, но более понятным способом:UINT uFlags;if (dis->itemState & ODS_INACTIVE && (((FALSE)))) uFlags = DST_ICON | DSS_DISABLED;else uFlags = DST_ICON | DSS_NORMAL;Да, это длиннее, но зато легко читается, а слово FALSE лучше заметно.
  • 10. 4. Выравнивайте в коде всё, что возможноВыравнивание кода уменьшает вероятность допустить опечатку или ошибки при Copy-Paste. Еслиошибка все же будет сделана, то найти её в процессе обзора кода будет в несколько раз проще.Рассмотрим пример кода.V537 Consider reviewing the correctness of maxX items usage. clist_modern modern_skinengine.cpp2898static BOOL ske_DrawTextEffect(...){ ... minX=max(0,minX+mcLeftStart-2); minY=max(0,minY+mcTopStart-2); maxX=min((int)width,maxX+mcRightEnd-1); maxY=min((int)height,maxX+mcBottomEnd-1); ...}Монолитный фрагмент кода, читать который совершенно не интересно. Отформатируем его:minX = max(0, minX + mcLeftStart - 2);minY = max(0, minY + mcTopStart - 2);maxX = min((int)width, maxX + mcRightEnd - 1);maxY = min((int)height, maxX + mcBottomEnd - 1);Это не самый показательный пример, но согласитесь, теперь стало намного проще заметить, чтодва раза используется переменная maxX.Мою рекомендацию по выравниванию не стоит понимать буквально и везде строить столбцыкода. Во-первых, это требует дополнительного времени при написании и правке кода. Во-вторых,это может привести к другим ошибкам. В следующем примере, как раз желание сделать красивыйстолбик привело к возникновению ошибки в коде Miranda IM.V536 Be advised that the utilized constant value is represented by an octal form. Oct: 037, Dec: 31. msnmsn_mime.cpp 192static const struct _tag_cpltbl{ unsigned cp; const char* mimecp;
  • 11. } cptbl[] ={ { 037, "IBM037" }, // IBM EBCDIC US-Canada { 437, "IBM437" }, // OEM United States { 500, "IBM500" }, // IBM EBCDIC International { 708, "ASMO-708" }, // Arabic (ASMO 708) ...}Желая сделать красивую колонку чисел, легко увлечься и вписать в начале 0, сделав константувосьмеричной.Уточняю рекомендацию. Выравнивайте в коде всё, что возможно. Но не выравнивайте числа,дописывая нули.5. Не размножайте строку более, чем один разКопирование строк при программировании неизбежно. Но можно подстраховать себя, невставляя строку из буфера обмена сразу несколько раз. В большинстве случаев лучше скопироватьстроку, затем отредактировать. Вновь скопировать и отредактировать. И так далее. Так труднейзабыть что-то изменить в строке или изменить её неправильно. Рассмотрим пример кода:V525 The code containing the collection of similar blocks. Check items 1316, 1319, 1318, 1323,1323, 1317, 1321 in lines 954, 955, 956, 957, 958, 959, 960. clist_modern modern_clcopts.cpp 954static INT_PTR CALLBACK DlgProcTrayOpts(...){ ... EnableWindow(GetDlgItem(hwndDlg,IDC_PRIMARYSTATUS),TRUE); EnableWindow(GetDlgItem(hwndDlg,IDC_CYCLETIMESPIN),FALSE); EnableWindow(GetDlgItem(hwndDlg,IDC_CYCLETIME),FALSE); EnableWindow(GetDlgItem(hwndDlg,IDC_ALWAYSPRIMARY),FALSE); EnableWindow(GetDlgItem(hwndDlg,IDC_ALWAYSPRIMARY),FALSE); EnableWindow(GetDlgItem(hwndDlg,IDC_CYCLE),FALSE); EnableWindow(GetDlgItem(hwndDlg,IDC_MULTITRAY),FALSE); ...}
  • 12. Скорее всего, никакой настоящей ошибки здесь нет. Просто два раза работаем с элементомIDC_ALWAYSPRIMARY. Тем не менее, ошибиться в подобных блоках из скопированных строквесьма легко.6. Выставляйте высокий уровень предупреждений у компилятора ииспользуйте статические анализаторыПо поводу многих ошибок невозможно дать рекомендаций, как снизить вероятность их появленияв коде. Чаще всего они представляют собой опечатки, которые допускает как новичок, так и самыйвысококвалифицированный программист.Тем не менее, многие из этих ошибок можно обнаружить ещё на этапе написания кода. В первуюочередь - это предупреждения компилятора. А во вторую - отчеты от ночного запуска статическиханализаторов кода.Сейчас кто-то скажет, что это плохо скрываемая реклама. Но на самом деле это просто ещё однарекомендация, которая позволяет сократить количество ошибок. Если я нашел ошибки, используястатический анализ, и не могу сказать, как избежать их появления в коде, то значитрекомендацией как раз и является использование анализаторов кода.Рассмотрим некоторые примеры ошибок, которые могут быть оперативно выявленыанализаторами исходного кода:V560 A part of conditional expression is always true: 0x01000. tabsrmm tools.cpp 1023#define GC_UNICODE 0x01000DWORD dwFlags;UINT CreateGCMenu(...){ ... if (iIndex == 1 && si->iType != GCW_SERVER && !(si->dwFlags && GC_UNICODE)) { ...}Допущена опечатка. Вместо оператора & используется оператор &&. Как здесь подстраховатьсяпри написании кода, я не знаю. Корректный вариант условия:
  • 13. (si->dwFlags & GC_UNICODE)Следующий пример.V528 It is odd that pointer to char type is compared with the 0 value. Probably meant: *str != 0.clist_modern modern_skinbutton.cpp 282V528 It is odd that pointer to char type is compared with the 0 value. Probably meant: *endstr !=0. clist_modern modern_skinbutton.cpp 283static char *_skipblank(char * str){ char * endstr=str+strlen(str); while ((*str== || *str==t) && str!=0) str++; while ((*endstr== || *endstr==t) && endstr!=0 && endstr<str) endstr--; ...}В этом коде всего лишь забыты две звездочки * для разыменования указателей. Результат можетбыть фатальным. Этот код предрасположен к access violation. Корректный вариант кода:while ((*str== || *str==t) && *str!=0) str++;while ((*endstr== || *endstr==t) && *endstr!=0 && endstr<str) endstr--;Здесь вновь сложно дать совет, кроме использования специальных инструментов для проверкикода.Следующий пример.V514 Dividing sizeof a pointer sizeof (text) by another value. There is a probability of logical errorpresence. clist_modern modern_cachefuncs.cpp 567
  • 14. #define SIZEOF(X) (sizeof(X)/sizeof(X[0]))int Cache_GetLineText(..., LPTSTR text, int text_size, ...){ ... tmi.printDateTime(pdnce->hTimeZone, _T("t"), text, SIZEOF(text), 0); ...}На первый взгляд все хорошо. В функцию передается текст и его длина, посчитанная с помощьюмакроса SIZEOF. На самом деле макрос следует назвать COUNT_OF, но не в этом дело. Беда в том,что мы пытаемся посчитать количество символов в указателе. Здесь вычисляется "sizeof(LPTSTR) /sizeof(TCHAR)". Человек такие подозрительные места замечает плохо, а вот компилятор истатический анализатор хорошо. Исправленный вариант кода:tmi.printDateTime(pdnce->hTimeZone, _T("t"), text, text_size, 0);Следующий примерV560 A part of conditional expression is always true: 0x29. icqoscar8 fam_03buddy.cpp 632void CIcqProto::handleUserOffline(BYTE *buf, WORD wLen){ ... else if (wTLVType = 0x29 && wTLVLen == sizeof(DWORD)) ...}Здесь уместна рекомендация писать константу в условии на первом месте. Вот такой код простоне скомпилируется:if (0x29 = wTLVType && sizeof(DWORD) == wTLVLen)Но многим программистам, и мне в их числе, такой стиль не нравится. Например, мне он мешаетчитать код, так как вначале я хочу узнать, какую переменную сравнивают, а только потом с чемименно.
  • 15. Если программист отказывается от такого стиля сравнений, то ему остается или полагаться накомпилятор/анализатор, или рисковать.Кстати, несмотря на то, что про эту ошибку все знают, она не самая редкая. Ещё три примера изMiranda IM, где анализатор PVS-Studio выдал предупреждение V559:else if (ft->ft_magic = FT_MAGIC_OSCAR)if (ret=0) {return (0);}if (Drawing->type=CLCIT_CONTACT)Анализ кода позволяет также выявить если и не ошибки, то очень подозрительные места в коде.Например, в Miranda IM указатели используют далеко не только как указатели. Если в однихместах кода такие игры выглядят нормально, то в других пугают. Пример кода, который менякрайне настораживает:V542 Consider inspecting an odd type cast: char * to char. clist_modern modern_toolbar.cpp 586static voidsttRegisterToolBarButton(..., char * pszButtonName, ...){ ... if ((BYTE)pszButtonName) tbb.tbbFlags=TBBF_FLEXSIZESEPARATOR; else tbb.tbbFlags=TBBF_ISSEPARATOR; ...}Фактически мы проверяем, что адрес строки не кратен 256. Мне не понятно, что на самом делехотели написать в условии. Возможно, это даже правильно, но сильно сомнительно.Анализом кода можно найти немало некорректных условий. Пример:V501 There are identical sub-expressions user->statusMessage to the left and to the right of the &&operator. jabber jabber_chat.cpp 214void CJabberProto::GcLogShowInformation(...)
  • 16. { ... if (user->statusMessage && user->statusMessage) ...}И так далее и так далее. Я могу ещё долго приводить другие примеры, но это не имеет смысла.Важно то, что большое количество ошибок можно обнаружить с помощью статического анализана самых ранних этапах.Когда в вашей программе статический анализатор находит мало ошибок, то его использование некажется интересным. Но это неверный путь в рассуждениях. Ведь многие ошибки, которыеанализатор смог бы найти на ранних этапах, вы исправляли потом и кровью, отлаживаясь часами.Статический анализ представляет больший интерес в процессе разработки программы, а не вкачестве разовых проверок. Множество ошибок и опечаток находится в процессе тестирования исоздания юнит-тестов. Но если часть из этих ошибок можно найти ещё на этапе написания кода, тоэто будет колоссальный выигрыш времени и сил. Обидно два часа отлаживать программу, чтобыпотом заметить лишнюю точку с запятой ; после оператора for. Такую ошибку можно частообезвредить, потратив 10 минут на статический анализ измененных в процессе работы файлов.ЗаключениеВ этой статье я поделился только некоторыми мыслями по поводу того, как допускать меньшеошибок при программировании на Си++. Зреют и другие мысли, о которых я постараюсь написатьв следующих статьях и заметках.P.S.Уже стало традицией, что после подобной статьи, кто-то спрашивает, а сообщили ли выразработчикам программы/библиотеки о найденных ошибках. Заранее отвечу на вопрос,отправили ли мы баг репорт по проекту Miranda IM.Нет, не отправили. Это слишком ресурсоёмкая задача. В статье показана только малая часть оттого, что было найдено. В проекте около ста фрагментов, где стоит поправить код, и более ста, гдея просто не знаю, ошибка это или нет. Однако, перевод этой статьи будет отправлен авторамMiranda IM и им будет предложена бесплатная версия анализатора PVS-Studio. Если они проявятинтерес, то смогут сами проверить исходный код и исправить то, что сочтут нужным.Ещё стоит пояснить, почему я часто не берусь судить ошибка тот или иной участок кода или нет.Пример неоднозначного кода:V523 The then statement is equivalent to the else statement. scriver msglog.c 695if ( streamData->isFirst ) {
  • 17. if (event->dwFlags & IEEDF_RTL) { AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "rtlpar"); } else { AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "ltrpar"); }} else { if (event->dwFlags & IEEDF_RTL) { AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "rtlpar"); } else { AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "ltrpar"); }}Вот два одинаковых фрагмента кода. Возможно, это ошибка. А возможно, сейчас в любой веткенужен одинаковый набор действий. И код написан специально так, чтобы потом его было простомодифицировать. Здесь необходимо знать программу, чтобы разобраться ошибочно это местоили нет.

×