Введение
Си — мощный, но требовательный язык. Прямое управление памятью и отсутствие «защиты от дурака» делают его уязвимым к ошибкам, которые могут привести к падению программы, утечкам памяти или даже уязвимостям. В этой статье разберём типичные ошибки новичков и способы их избежать.
1. Утечки памяти: забытый free()
Динамическая память выделяется через malloc, calloc или realloc, но её нужно освобождать вручную.
Пример ошибки:
void create_array() {
int *arr = malloc(10 * sizeof(int));
// ... работа с массивом, но забыли free(arr)
}
После вызова функции память останется занятой до завершения программы.
Как исправить:
Всегда освобождайте память через free().
Используйте valgrind для поиска утечек.
Правильный код:
void create_array() {
int *arr = malloc(10 * sizeof(int));
if (arr == NULL) {
// Обработка ошибки аллокации
return;
}
// ... работа с массивом
free(arr); // Освобождаем!
}
2. Use-After-Free: обращение к освобождённой памяти
Ошибка:
int *ptr = malloc(sizeof(int));
free(ptr);
*ptr = 42; // Опасно: запись в освобождённую память!
Это вызывает неопределённое поведение (UB).
Решение:
-
После free() присваивайте указателю NULL:
free(ptr); ptr = NULL; // Теперь попытка *ptr = 42 вызовет segfault.
3. Выход за границы массива
Пример:
int arr[5];
for (int i = 0; i <= 5; i++) { // Ошибка: i <= 5
arr[i] = i; // При i=5 выходим за границы
}
Как избежать:
-
Используйте константы или sizeof для определения размера:
#define ARR_SIZE 5 int arr[ARR_SIZE]; for (int i = 0; i < ARR_SIZE; i++) { ... }
Для динамических массивов храните размер отдельно.
4. Неинициализированные указатели
Ошибка:
int *ptr; // Не инициализирован
*ptr = 10; // Запись в случайный адрес → UB!
Решение:
-
Всегда инициализируйте указатели:
int *ptr = NULL; // Теперь попытка записи вызовет ошибку.
-
Проверяйте указатель перед использованием:
if (ptr != NULL) { *ptr = 10; }
5. Динамическая память в функциях: кто отвечает за free?
Опасный пример:
int* create_array(int size) {
int *arr = malloc(size * sizeof(int));
return arr;
}
void main() {
int *data = create_array(10);
// ... забыли free(data) → утечка!
}
Как избежать:
Договоритесь, кто освобождает память: функция или вызывающий код.
-
Используйте комментарии:
// Возвращает указатель на массив. Память должна быть освобождена через free()! int* create_array(int size);
6. Магия чисел и «забытый break в switch
Ошибка:
switch (status) {
case 1:
printf("OK");
case 2: // Забыли break!
printf("Error");
break;
}
Если status=1, выполнится и case 1, и case 2.
Решение:
Всегда добавляйте break, если это не преднамеренно.
-
Заменяйте «магические числа» на константы:
#define STATUS_OK 1 #define STATUS_ERROR 2
7. Переполнение буфера
Пример:
char buffer[10];
scanf("%s", buffer); // Если ввести больше 9 символов → переполнение!
Как исправить:
-
Используйте функции с ограничением размера:
fgets(buffer, sizeof(buffer), stdin);
-
Или явно указывайте лимит в scanf:
scanf("%9s", buffer); // Максимум 9 символов
Инструменты, которые спасут вас
-
Valgrind — ищет утечки и невалидные операции с памятью:
valgrind --leak-check=full ./your_program
-
Compiler Warnings — включает предупреждения в GCC/Clang:
gcc -Wall -Wextra -Werror your_code.c
-
Cppcheck — статический анализатор:
cppcheck --enable=all your_code.c
Заключение
Проверяйте указатели перед использованием.
Освобождайте память сразу, как она стала ненужной.
Тестируйте код с помощью Valgrind и включайте все предупреждения компилятора.
Избегайте магии — используйте константы и понятные имена переменных.
Си требует дисциплины, но взамен даёт полный контроль над системой. Учитесь на чужих ошибках, а не на своих!
Полезные ресурсы:
Книга «Язык программирования Си» (K&R).
Документация Valgrind: https://www.valgrind.org/.
Онлайн-чекер Cppcheck: https://cppcheck.sourceforge.io/.
Комментарии (18)
zatim
19.02.2025 18:46В большинстве случаев на Си пишут для встраиваемых систем, а у них объем памяти фиксирован и заранее известен, поэтому нет необходимости использовать динамическое выделение памяти. По этой же причине можно использовать глобальные переменные и полностью отказаться от указателей. Большая часть описанных в статье проблем теряют актуальность.
arielf
19.02.2025 18:46Вы не поверите, но практически всё окружение и пользовательские программы в UNIX, почти все операционные системы, их компиляторы, многие научные и иные библиотеки написаны на C.
TIOBE C по популярности такой же как и Java.
TIOBE
juramehanik
19.02.2025 18:46Прекрасные примеры, которые поймает статический анализатор, а есть что поинтереснее?
Serpentine
19.02.2025 18:46Вы устали от:❌ Тайных утечек памяти, которые пожирают ресурсы.
❌ Загадочных падений программы без объяснения причин.
❌ Указателей-призраков, стреляющих в вас из темноты сегфолтов.
Для кого:
• Начинающие разработчики, которые хотят писать код, а не баги.
• Те, кто считает, что free() — это про свободу, а не про память.
• Все, кто устал гуглить «почему Си опять вылетает».
Взаимоисключающие параграфы, тем более после прочтения статьи. Любой уважающий себя автор сишного букваря (или цикла уроков) эти "ошибки" озвучит в начале соответствующих тем.
ChatGPT мне по этому промту (название статьи) информации больше накидал и подробнее. Только он не боялся слова "разыменование" и не путал тестирование с профилированием и отладкой.
Выход за границы массива
Как избежать:
Используйте константы
Так?
#define ARRAY_SIZE 10 /* some code */ int my_best_array[ARRAY_SIZE] = {0}; for(int i = 0; i <= ARRAY_SIZE; i++) printf("%d\n", my_best_array[i]); /* some code else */
или sizeof для определения размера:
Сколько нулей выведет?
void print_my_best_array(int best_array[]) { for(int i = 0; i < sizeof(best_array); i++) printf("%d\n", best_array[i]); } int main() { int my_best_array[10] = {0}; print_my_best_array(my_best_array); return 0; }
Полезные ресурсы:
Книга «Язык программирования Си» (K&R).
Документация Valgrind: https://www.valgrind.org/.
Онлайн-чекер Cppcheck: https://cppcheck.sourceforge.io/.
Можно еще ссылок на документацию к GCC и Clang дать, в статье же про Compiler Warnings было.
Ну и K&R отлично подойдет "для тех, кто считает, что free() — это про свободу".
sic
19.02.2025 18:46Никогда не освобождаю память (и у меня все работает). Как? Неужели стоит написать статью об этом? #NothingIsFree
MonkeyWatchingYou
Ещё не суйте сырые руки в щиток.
Но! Если написать маркером "UNSAFE" на руке, то можно. Новомодные языки такое практикуют.