Чому студентам потрібен аналізатор коду CppCat

CppCat для навчання
CppCat — це простий статичний аналізатор коду для пошуку помилок в програмах на мові Сі/Сі++. Ми почали видавати безкоштовні академічні ліцензії всім бажаючим (студентам, викладачам і так далі). Для більшої популяризації CppCat серед студентів я вирішив написати цю замітку про помилки, які можна знайти в лабораторних роботах, що зустрічаються на сайті Pastebin.com.

Трохи про CppCat
CppCat — статичний аналізатор коду, інтегрується в середовище Visual Studio дозволяє знайти безліч орфографічних та інших помилок ще на етапі кодування. Аналізатор вміє запускатися автоматично після компіляції і перевіряти тільки що написаний код. Аналізатор підтримує C, C++, C++/CLI, C++/CX.

Про те, як отримати безкоштовну ліцензію на CppCat, описано в статті: Безкоштовний CppCat для студентів. Хочу додати, що ми даємо ліцензію не тільки студентам, але й аспірантам, викладачам і так далі.

Багатьох турбує, що CppCat не інтегрується в безкоштовну середовище Visual Studio Express. На жаль, ми не можемо нічого з цим вдіяти. Express-версії Visual Studio не підтримують модулі розширень (plugins). Однак, це не біда. Хочеться нагадати, що студенти мають доступ до Microsoft DreamSpark і можуть отримати доступ до Visual Studio Professional.

Заманювання студентів
На початку хотів написати щось в дусі:

Навіть студент може отримати користь від статичного аналізу. Навіщо довго і болісно шукати помилку в своїй програмі, якщо про неї може підказати CppCat? Таким чином можна не тільки швидше знайти помилку, але ще дізнатися подробиці про те, як робити не треба. Документація до CppCat докладно пояснює кожну діагностику і наводить різні приклади помилок, підказує, як їх виправляти.

Потім вирішив, що це вимушено. Ну які у студентів серйозні помилки? Можна і поотлаживать цикл з 10 ітерацій. Це буде навіть корисно. Тому я переформулюю рекомендацію використовувати CppCat наступним чином:

Роботодавець буде цінувати не тільки ваше вміння програмувати і писати хитрі алгоритми. Не менш важливо вміти користуватися базовим інструментарієм.

Гріш ціна алгоритмом, якщо він буде втрачений із-за того, що ви не знаєте, що таке система контролю версій і максимум, що робили — робили копії вихідних кодів на флешку.

Тому важливо вивчати не тільки мови та середовища розробки, але і допоміжний інструментарій.

Я не беруся складати список найбільш важливого інструментарію. Однак важливо знати хоча б одну систему контролю версій, навіщо може бути потрібен WinMerge, що таке профайлер, як зробити дистрибутив і так далі.

Однією з технологій, яку добре знати і можна згадати в резюме, є статичний аналіз коду. CppCat відмінно підходить для знайомства з методологією статичного аналізу. Це буде хорошим плюсом до ваших знань і натякне роботодавцю, що ви щось чули про якість коду.

А тепер те, заради чого ми тут зібралися
Нудна частина закінчилася. Я думаю, ви здогадуєтеся, що зараз ми будемо шукати помилки в лабораторних роботах.

Говорити про те, що код часто поганий, в цей раз я не буду. І так зрозуміло, що код лабораторних містить у собі темряву найрізноманітніших помилок. Тому я поставив завдання не знайти як можна більше помилок. Це нецікаво. Я спробував виділити якісь патерни помилок, які проявляються у студентів найчастіше. Правда, поки я зміг чітко помітити переважання помилок лише трьох типів. Але про це трохи нижче.

У Вас може питання: звідки я взяв лабораторні роботи? Відповідаю.

Є сайт Pastebin.com, де розробники можуть зручно обмінюватися фрагментами коду. Студенти активно використовують цей сайт. Майже весь код, який має тег C++, являє собою якусь лабораторну роботу або її частину.

Ми написали програму, яка стежить за сайтом Pastebin.com і викачує звідти свіжі файли, позначені «код C++». Назбиравши більше двох тисяч файлів, я зробив з них проект в Visual Studio і перевірив його. Більше половини, звичайно, не вдалося перевірити. У багатьох файлах тільки фрагменти коду, вписаний текст, який не є коментарем, не вистачає якихось бібліотек так далі. Однак я й не ставив за мету перевірити як можна більше коду, опублікованого на Pastebin.com. Те, що проверилось, мені цілком вистачить для цієї статті. Збір файлів триває і, можливо, я потім напишу ще щось на цю тему.

Типові помилки студентів, що вивчають Сі++
Я переглянув не так вже багато помилок в лабораторних роботах. Так що поки можу виділити лише 3 патерну.

Приводити все підряд приклади помилок я не буду. Вони однотипні і нецікаві. Обмежуся лише кількома прикладами. Але, повірте, якщо я кажу, що така помилка поширена, це дійсно так.

P. S. Багато з опублікованих прикладів були з обмеженням по часу і вже недоступні. Тому посилання на вилучені сторінки давати не буду.

Патерн 1. Третє місце за популярністю. Плутанина в однотипних умовах
Багато завдання з програмування передбачають перевірку багатьох умов. І, реалізуючи їх, легко заплутатися або опечататься. Показовий приклад:
int main()
{
int n,a,b,c;
cin >> n;
for(int i=0;i < n;i++)
{
cin >> a >> b >> c;
if((a % 2==0 && b % 2 ==0 && c % 2!=0)||
(a % 2==0 && b % 2!=0 && c % 2==0)||
(a % 2!=0 && b % 2==0 && c % 2==0)||
(a % 2!=0 && b % 2 !=0 && c % 2==0)||
(a % 2==0 && b % 2!=0 && c % 2!=0)|| <<<---
(a % 2==0 && b % 2!=0 && c % 2!=0)) <<<---
{
cout << "1";
}
else
cout << "2";
}
cout << endl;
return 0;
}

Попередження CppCat: V501 There are identical sub-expressions '(a % 2 == 0 && b % 2 != 0 && c % 2 != 0)' to the left and to the right of the '||' operator. jtzrihcg.cpp 14

Треба було якось хитро перевірити значення трьох введених змінних. Швидше за все, код копіювався і не скрізь був правильно поправлений. В результаті передостання та остання рядок в умови збігаються.

Ще приклад:
int main() {
....
} else if(gesucht < geraten) {
puts("Ein bisschen zu klein");
} else if (gesucht < geraten) {
puts("Ein bisschen zu gross");
}
....
}

V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 41, 43. wrgkuuzr.cpp 40

Два рази виконується перевірка (gesucht < geraten), але при цьому повинні виводитися різні рядки.

До речі, в обох прикладах помилка знаходиться у останньому рядку. Знову нам зустрівся "ефект останнього рядка".

Патерн 2. Друге місце за популярністю. Вихід за кордон масиву на 1 елемент
Те, що елементи масивів нумеруються в мові C++ від нуля, є великим утрудненням при його вивченні. Тобто начебто зрозуміти це просто, але от навчитися не виходити за кордон масиву дуже складно. Якщо потрібен 10 елемент масиву, то так і хочеться написати A[10]. Приклад:
int main()
{
....
int rodnecs[10];
....
VelPol1 = rodnecs[1] + rodnecs[3] + rodnecs[5] +
rodnecs[8] + rodnecs[10];
....
}

Попередження CppCat: V557 Array overrun is possible. The '10' index is pointing beyond array bound. 0z3x9b3i.cpp 38

:
void main()
{
....
//evaulation
double calc_y[3];
for (int i = 0; i < 3; i++)
{
calc_y[i] = F(pop[i][1], pop[i][2], pop[i][3], x[i]);
}
....
}

Попередження CppCat: V557 Array overrun is possible. The '3' index is pointing beyond array bound. 1uj9v9xs.cpp 48

Багато неправильних порівнянь в умовах циклів:
int main()
{
int i,pinakas[20],temp,temp2,max,min,sum=0;
for (i=1;i < =20;i++)
{
pinakas[i]=rand();
......
}

Попередження CppCat: V557 Array overrun is possible. The value of 'i' index could reach 20. 287ep6c0.cpp 20

Дуже багато:
int main()
{
const int arraySize = 10;
int a[arraySize];
int key,index,to_do = arraySize - 1;
bool did_swap = true;

srand(time(NULL));
for (int i = 0; i <= arraySize; i++)
{
//generating random number between 1 - 100
a[i] = rand() % 100 + 1;
}
....
}

Попередження CppCat: V557 Array overrun is possible. The value of 'i' index could reach 10. wgk1lx3u.cpp 18

Інші помилки аналогічні наведеним вище, так що закінчимо.

Патерн 1. Перше місце за популярністю. Неініціалізовані змінні
Про! Я, здається, зрозумів, чому, кого не запитай, однією з найбільш частих і небезпечних помилок програмування на Сі/Сі++ люди називають «неініціалізовані змінні». Але, при цьому, аналізуючи проекти з допомогою PVS-Studio, я рідко зустрічаю цю помилку.

Чому? Мабуть, при вивченні мови все дуже сильно набивають собі шишки на цьому. Таким чином, програмісти потім майже не допускають такі помилки. Але спогади залишилися. І на питання, чого треба боятися, часто відповідають «неініціалізовані змінні».

Є зовсім просте:
int main()
{
....
int n,k=0, liczba=n, i=1;
....
}

Попередження CppCat: V614 Uninitialized variable 'n' used. 1hvefw6r.cpp 92

Можна неправильно працювати зі списком:
void erase(List * Lista){
List* pom;
pom->next = Lista->next;
Lista->next= pom;
delete pom;
}

Попередження CppCat: V614 Uninitialized pointer 'pom' used. 6gpsgjuy.cpp 54

Можна зробити цикл з випадковим кількістю ітерацій:
void main()
{
int i,n;
imie* ime[20];
string nazwa;
string kobieta="Kobiece imina: ";
wpr_dane();
for (i = 1; i < n; i++)
{
....
}

Попередження CppCat: V614 Uninitialized variable 'n' used. 8kns8hyn.cpp 63

спочатку використовувати, а потім вводити значення змінної:
int main() {
int n1;
int n2;
std::vector < int> vec1(n1);
std::vector < int> vec2(n2);
std::cin >> n1;
for (int i = 0; i < n1; i++) {
std::cin >> vec1[i];
}
std::cin >> n2;
for (int j = 0; j < n2; j++) {
std::cin >> vec2[j];
}
....
}

Попередження CppCat:
  • V614 Uninitialized variable 'n1' used. 9r9zdkp6.cpp 25
  • V614 Uninitialized variable 'n2' used. 9r9zdkp6.cpp 26
Далі наводити приклади, думаю, сенсу немає. Але, повірте, студенти відстрілюють собі ноги неинициализированными змінними найрізноманітнішими способами.

Інші помилки
Звичайно, в лабораторних я побачив багато інших найрізноманітніших помилок. Таких же великих груп помилок, як описано вище, я виділити не можу. Хоча я можу назвати ще кілька помітних груп: неправильне обчислення розміру масивів, крапка з комою, дострокове переривання циклу, неправильна робота з масивами, WTF.

Неправильне обчислення розміру масивів
Багатьом початківцям важко дається розуміння, що в Сі/Сі++ покажчик та масив — це різні сутності. В результаті нерідко трапляється код подібний :
int arrayLen(int p[])
{
return(sizeof(p)/sizeof(*p));
}

Попередження CppCat: V511 The sizeof() operator returns size of the pointer, and not of the array, in 'sizeof (p)' expression. seprcjvw.cpp 147

Правда, функція arrayLen() ніде не використовується. Мабуть, з-за того, що не працює. :)

Ще один приклад:
bool compare_mas(int * mas, int * mas2){
//обчислюємо кількість елементів першого масиву
const auto mas_size = sizeof(mas) / sizeof(mas[0]);

//обчислюємо кількість елементів другого масиву
const auto mas2_size = sizeof(mas2) / sizeof(mas2[0]);
....
}

Попередження CppCat:
  • V514 Dividing sizeof a pointer 'sizeof (mas)' by another value. There is a probability of logical error presence. 0mxbjwbg.cpp 2
  • V514 Dividing sizeof a pointer 'sizeof (mas2)' by another value. There is a probability of logical error presence. 0mxbjwbg.cpp 3

Не там поставлена крапка з комою ';'
Ці помилки трапляються не так часто, як я очікував. Вони є, але поширеною помилкою в лабораторних роботах я її назвати не можу.

Типовий приклад:
vector sum(vector m[],int N){
vector sum,tmp;
for (int i=0;i < N;i++);
{
tmp.a=m[i].a;
tmp.b=m[i].b;
tmp.c=m[i].c;
sum.a+=tmp.a;
sum.b+=tmp.b;
sum.c+=tmp.c;
}
return sum.a,sum.b,sum.c;
}

Попередження CppCat: V529 Odd semicolon ';' after 'for' operator. knadcqde.cpp 122

Дострокове переривання циклу
Є ряд прикладів, де цикл випадково переривається раніше часу:
int main()
{
....
for (long long j = sled.size()-1; j > i; j --)
{
sled[j] = '0';
des = 1;
break;
}
....
}

Попередження CppCat: V612 An unconditional 'return' within a loop. m6ze6bxd.cpp 40

Неправильна робота з масивами
У декількох лабораторних зустрілася робота з масивами в стилі Pascal. Тобто використовується кома, що хоч і компілюється, але працює, звичайно, неправильно:
void build_maze(){
// tablica przechowujaca informacje o odwiedzonych polach
bool ** tablica = new bool *[n];
....
if (tablica[aktualny.x - 1, aktualny.y] == false){
....
}

Попередження CppCat: V520 The comma operator ',' in array index expression '[aktualny.x — 1, aktualny.y]'. qqxjufye.cpp 125

Або забувають, що пам'ять під які повертаються масиви треба виділяти спеціальним чином:
int *mul3(int *a)
{
int mem = 0;
int b[1001];
for (int i = 100; i >= 0; i--)
{
int x = a[i] * 3 + mem;
mem = x / 10;
b[i] = x % 10;
}
return b;
}

Попередження CppCat: V558 Function returns the pointer to temporary local object: b. hqvgtwvr.cpp 89

WTF
Є фрагменти коду, які я, крім як WTF, назвати не можу. Можливо, хтось попросив одногрупника пояснити, де в програмі помилка. Хоча, скоріше, ця лабораторна як раз на вивчення переповнення масивів. На жаль, я не знаю, що написано в коментарі.

Наведу цілком один такий приклад:
#include < iostream>
using namespace std;
int main()
{
int a[10];
for(int i=0; i<50; i++)
cout << a[i] << endl;
//ovoj loop ili kje krashne ili kje ti nedefinirani vrednost
//(ne mora da bidat 0)
//ako namesto 50 stavis 500000, skoro sigurno kje krashne
int b[10];
for(int i=0; i<50; i++)
{
b[i] = i;
cout << b[i] << endl;
}
//ovoj loop nekogas kje raboti, nekogas ne. problemot so
//out-of-bounds index errori e sto nekogas rabotat kako
//sto treba, pa greskata tesko se naogja
}

Що ще не ввійшло статтю
Багато що не увійшло! Наприклад, зустрічається неправильне використання функцій printf(). Але це настільки банально, що навіть писати про це не хочеться.

Втім, зустрічалися і досить екзотичні різновиди помилок:
void zmienne1()
{
....
int a,b,c,d;
cin >> a >> b >> >> d;
if(a == b == c == d)
....
}

Попередження CppCat: V709 Suspicious comparison found: 'a == b == c'. Remember that 'a == b == c' is not e qual to 'a == b && b == c'. b5lt64hj.cpp 284

Також з рідкісного (якщо, звичайно, не дивитися на попередження компілятора):
const long AVG_PSYCHO = 0.8;
const long AVG_GRAD = 1.2;

Попередження CppCat:
  • V674 The '0.8' literal of the 'double' type is assigned to a variable of the 'long' type. Consider inspecting the '= 0.8' expression. 2k2bmnpz.cpp 21
  • V674 The '1.2' literal of the 'double' type is assigned to a variable of the 'long' type. Consider inspecting the '= 1.2' expression. 2k2bmnpz.cpp 22
Тим не менше, треба закінчувати. Сподіваюся, я розважив читачів і спокусив когось спробувати CppCat.

Чому ми не плануємо робити будь-які online-аналізатор
Я передбачаю запитання: «Чому б вам не зробити якусь систему для онлайн-перевірки коду?». Наприклад, є якась форма, куди можна вставити код і натиснути кнопку «перевірити». Або ви моніторите сайт pastebin.com так чому б не викладати куди-то результати перевірки?

Я впевнений, що робити цього нам не потрібно. Для цього є три причини, тому прошу не починати дискусію на цю тему.

Причини:
  1. Це не потрібно ні нам, ні користувачеві. Нам це додасть роботи. А користувач не отримає нічого нового. Він може просто завантажити та встановити PVS-Studio або CppCat і провести всі експерименти, які забажає. Демонстраційної версії буде більш ніж достатньо. Часто форми «встав і перевір код» роблять ті, у кого просто так не можна завантажити пробну версію. У нас можна. Більш того, вона не має будь-яких функціональних обмежень. Ще хтось може сказати, що у нього немає Windows, а він хотів щось спробувати. Але раз у нього немає Windows, то він все одно не наш користувач.
  2. Така система сильно спотворює оцінку можливостей статичного аналізатора. Стаття на цю тему: Міфи про статичному аналізі. Міф п'ятий — можна скласти маленьку програму, щоб оцінити інструмент. Ми хочемо, щоб люди відчували аналізатор на своїх реальних проектах, а не на синтетичних прикладах.
  3. Як я вже сказав, синтетичні приклади ми перевіряти не хочемо. А перевірити проект цілком складно з інфраструктурної точки зору. Докладніше про це написано в інтерв'ю. Коротко: доведеться робити складну систему, куди треба закачувати исходники, бібліотеки, налаштовувати параметри складання і так далі. Інакше неможливий повноцінний аналіз. Виходить, що простіше завантажити аналізатор, встановити і перевірити.

Висновок
Шановні студенти та викладачі, будемо раді бачити вас серед наших користувачів. Бажаю студентам стати кваліфікованими фахівцями і схилити надалі свій колектив на придбання PVS-Studio для командної роботи.

посилання:
  1. PVS-Studio для Visual C++.
  2. Альтернатива PVS-Studio за $250.
  3. Порівняння можливостей статичних аналізаторів коду PVS-Studio і CppCat.
  4. Безкоштовний CppCat для студентів.


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.