Найдите и объясните все ошибки в этом C‑фрагменте, указывающие на неопределённое поведение или утечки памяти: char *s = malloc(10); strcpy(s, "This string is too long"); free(s); printf("%s", s); — какие инструменты и приёмы вы бы использовали для диагностики и исправления
Ниже — разбор строк фрагмента и все ошибки UB/потенциальныеутечкиUB / потенциальные утечкиUB/потенциальныеутечки, а также приёмы и инструменты для поиска и исправления.
Исходный код: char *s = malloc101010; strcpy(s, "This string is too long"); freesss; printf("%s", s);
malloc101010 выделяет 10 байт.Строковый литерал "This string is too long" имеет 23 символа + 1 терминатор = 24 байта.strcpy копирует всю строку включая '\0' в выделенные 10 байт → запись за границы выделенной области → неопределённое поведение повреждениеheap−метаданных,коррупцияпамятиповреждение heap-метаданных, коррупция памятиповреждениеheap−метаданных,коррупцияпамяти.
2) Не проверяется результат malloc
Если malloc вернул NULL недостаточнопамятинедостаточно памятинедостаточнопамяти, то strcpyNULL,...NULL, ...NULL,... — чтение/запись по нулевому указателю → UB/crash.freeNULLNULLNULL безопасен, но до этого UB уже случится.
3) Use-after-free
freesss; затем printf("%s", s); — использование указателя на освобождённую память. Это UB. Возможны странные эффекты: данные могут ещё быть видимы, а могут быть перезаписаны/привести к краху.
4) printf("%s", s) после free не только UB, но и риск утечки/неопределённого вывода — программа может вывести мусор или упасть.
Утечекпамятивэтомнебольшомфрагментенет,еслиfreeдействительновыполняется.Ноесликодбудетразветвлёниfreeневызванвошибочномпути—возможнаутечка;такжееслиmallocвернулNULLипрограммасразузавершитсянеправильно,могутбытьпроблемы.Темнеменеездесьявнойутечкинет.Утечек памяти в этом небольшом фрагменте нет, если free действительно выполняется. Но если код будет разветвлён и free не вызван в ошибочном пути — возможна утечка; также если malloc вернул NULL и программа сразу завершится неправильно, могут быть проблемы. Тем не менее здесь явной утечки нет.Утечекпамятивэтомнебольшомфрагментенет,еслиfreeдействительновыполняется.Ноесликодбудетразветвлёниfreeневызванвошибочномпути—возможнаутечка;такжееслиmallocвернулNULLипрограммасразузавершитсянеправильно,могутбытьпроблемы.Темнеменеездесьявнойутечкинет.
Как исправить рекомендациирекомендациирекомендации
Выделять корректный объём: размер = strlensrcsrcsrc + 1.Проверять возвращаемое значение malloc.Не использовать память после free; переместить printf перед free или установить s = NULL после free.Избегать strcpy — использовать более безопасные аналоги (snprintf, memcpy + явное добавление '\0', strdup, asprintf). Учтите, что strncpy не гарантирует '\0' при усечении.Пример безопасного варианта:
const char src = "This string is too long"; size_t len = strlensrcsrcsrc; char s = malloclen+1len + 1len+1; if !s!s!s { perror("malloc"); exit111; } memcpys,src,len+1s, src, len + 1s,src,len+1; // копируем вместе с '\0' printf("%s\n", s); freesss; s = NULL;
Ещё вариант короче ипривычныйи привычныйипривычный:
char *s = strdup("This string is too long"); if !s!s!s { perror("strdup"); exit111; } printf("%s\n", s); freesss;
dup/strdupвыделяетпамятьикопирует,ноstrdup—POSIX,невстаромстандартеC89;asprintfтожеGNU−расширение.dup/strdup выделяет память и копирует, но strdup — POSIX, не в старом стандарте C89; asprintf тоже GNU-расширение.dup/strdupвыделяетпамятьикопирует,ноstrdup—POSIX,невстаромстандартеC89;asprintfтожеGNU−расширение.
Инструменты и приёмы для диагностики и исправления
AddressSanitizer ASanASanASan: gcc -fsanitize=address,undefined -g -O1 test.c -o test && ./test ASan быстро обнаружит переполнение буфера и use-after-free, даст стек вызовов и подсказки.UndefinedBehaviorSanitizer UBSanUBSanUBSan: -fsanitize=undefined полезносовместносASanполезно совместно с ASanполезносовместносASan.MemorySanitizer MSanMSanMSan — для неинициализированной памяти требуетclang,специальныеусловиятребует clang, специальные условиятребуетclang,специальныеусловия.
Valgrind
valgrind --leak-check=full ./a.out Находит invalid read/write, use-after-free, утечки памяти номедленнееиневсегдатакнагляденкакASanно медленнее и не всегда так нагляден как ASanномедленнееиневсегдатакнагляденкакASan.
Статические анализаторы
clang-tidy, clang static analyzer, cppcheck, Coverity — найдут некоторые потенциальные переполнения и неопределённые использования.
Отладчик
gdb — для пошагового анализа, анализа дампов.
Приёмы разработки
Unit-тесты и fuzzing — помогут поймать краевые случаи.Писать код так, чтобы владение памятью было очевидно вC—комментарии,строгиеконтрактныеправила;вC++—RAII/умныеуказателив C — комментарии, строгие контрактные правила; в C++ — RAII / умные указателивC—комментарии,строгиеконтрактныеправила;вC++—RAII/умныеуказатели.После free присваивать указателю NULL, чтобы повторный free был безопасен и попытка доступа легко детектировалась.
Как ASan/Valgrind сообщат про нашу проблему
ASan: сообщит о heap-buffer-overflow в strcpy и/или о use-after-free при printf, выведет backtrace.Valgrind: выдаст Invalid write / Invalid read и покажет, где была выделена память и где была освобождена.
Главные ошибки: переполнение буфера mallocслишкоммаленькийmalloc слишком маленькийmallocслишкоммаленький, отсутствие проверки malloc, использование памяти после free. Исправляется выделением нужного размера strlen+1strlen+1strlen+1, проверкой возврата malloc, перемещением printf перед free илиудалениемобращенияпослеfreeили удалением обращения после freeилиудалениемобращенияпослеfree, использованием безопасных функций копирования и санитайзеров для отладки.
Ниже — разбор строк фрагмента и все ошибки UB/потенциальныеутечкиUB / потенциальные утечкиUB/потенциальныеутечки, а также приёмы и инструменты для поиска и исправления.
Исходный код:
char *s = malloc101010;
strcpy(s, "This string is too long");
freesss;
printf("%s", s);
Ошибки и почему это UB
1) Недостаточный буфер — переполнение bufferoverflowbuffer overflowbufferoverflow
malloc101010 выделяет 10 байт.Строковый литерал "This string is too long" имеет 23 символа + 1 терминатор = 24 байта.strcpy копирует всю строку включая '\0' в выделенные 10 байт → запись за границы выделенной области → неопределённое поведение повреждениеheap−метаданных,коррупцияпамятиповреждение heap-метаданных, коррупция памятиповреждениеheap−метаданных,коррупцияпамяти.2) Не проверяется результат malloc
Если malloc вернул NULL недостаточнопамятинедостаточно памятинедостаточнопамяти, то strcpyNULL,...NULL, ...NULL,... — чтение/запись по нулевому указателю → UB/crash.freeNULLNULLNULL безопасен, но до этого UB уже случится.3) Use-after-free
freesss; затем printf("%s", s); — использование указателя на освобождённую память. Это UB. Возможны странные эффекты: данные могут ещё быть видимы, а могут быть перезаписаны/привести к краху.4) printf("%s", s) после free не только UB, но и риск утечки/неопределённого вывода — программа может вывести мусор или упасть.
Утечекпамятивэтомнебольшомфрагментенет,еслиfreeдействительновыполняется.Ноесликодбудетразветвлёниfreeневызванвошибочномпути—возможнаутечка;такжееслиmallocвернулNULLипрограммасразузавершитсянеправильно,могутбытьпроблемы.Темнеменеездесьявнойутечкинет.Утечек памяти в этом небольшом фрагменте нет, если free действительно выполняется. Но если код будет разветвлён и free не вызван в ошибочном пути — возможна утечка; также если malloc вернул NULL и программа сразу завершится неправильно, могут быть проблемы. Тем не менее здесь явной утечки нет.Утечекпамятивэтомнебольшомфрагментенет,еслиfreeдействительновыполняется.Ноесликодбудетразветвлёниfreeневызванвошибочномпути—возможнаутечка;такжееслиmallocвернулNULLипрограммасразузавершитсянеправильно,могутбытьпроблемы.Темнеменеездесьявнойутечкинет.
Как исправить рекомендациирекомендациирекомендации
Выделять корректный объём: размер = strlensrcsrcsrc + 1.Проверять возвращаемое значение malloc.Не использовать память после free; переместить printf перед free или установить s = NULL после free.Избегать strcpy — использовать более безопасные аналоги (snprintf, memcpy + явное добавление '\0', strdup, asprintf). Учтите, что strncpy не гарантирует '\0' при усечении.Пример безопасного варианта:const char src = "This string is too long";
Ещё вариант короче ипривычныйи привычныйипривычный:size_t len = strlensrcsrcsrc;
char s = malloclen+1len + 1len+1;
if !s!s!s { perror("malloc"); exit111; }
memcpys,src,len+1s, src, len + 1s,src,len+1; // копируем вместе с '\0'
printf("%s\n", s);
freesss;
s = NULL;
char *s = strdup("This string is too long");
if !s!s!s { perror("strdup"); exit111; }
printf("%s\n", s);
freesss;
dup/strdupвыделяетпамятьикопирует,ноstrdup—POSIX,невстаромстандартеC89;asprintfтожеGNU−расширение.dup/strdup выделяет память и копирует, но strdup — POSIX, не в старом стандарте C89; asprintf тоже GNU-расширение.dup/strdupвыделяетпамятьикопирует,ноstrdup—POSIX,невстаромстандартеC89;asprintfтожеGNU−расширение.
Инструменты и приёмы для диагностики и исправления
Компилятор и предупреждения
Включайте предупреждения: gcc -Wall -Wextra -Wpedantic -O2.Включите специфичные: -Wformat-overflow -Wstringop-overflow.Runtime sanitizers оченьэффективныочень эффективныоченьэффективны
AddressSanitizer ASanASanASan: gcc -fsanitize=address,undefined -g -O1 test.c -o test && ./testASan быстро обнаружит переполнение буфера и use-after-free, даст стек вызовов и подсказки.UndefinedBehaviorSanitizer UBSanUBSanUBSan: -fsanitize=undefined полезносовместносASanполезно совместно с ASanполезносовместносASan.MemorySanitizer MSanMSanMSan — для неинициализированной памяти требуетclang,специальныеусловиятребует clang, специальные условиятребуетclang,специальныеусловия.
Valgrind
valgrind --leak-check=full ./a.outНаходит invalid read/write, use-after-free, утечки памяти номедленнееиневсегдатакнагляденкакASanно медленнее и не всегда так нагляден как ASanномедленнееиневсегдатакнагляденкакASan.
Статические анализаторы
clang-tidy, clang static analyzer, cppcheck, Coverity — найдут некоторые потенциальные переполнения и неопределённые использования.Отладчик
gdb — для пошагового анализа, анализа дампов.Приёмы разработки
Unit-тесты и fuzzing — помогут поймать краевые случаи.Писать код так, чтобы владение памятью было очевидно вC—комментарии,строгиеконтрактныеправила;вC++—RAII/умныеуказателив C — комментарии, строгие контрактные правила; в C++ — RAII / умные указателивC—комментарии,строгиеконтрактныеправила;вC++—RAII/умныеуказатели.После free присваивать указателю NULL, чтобы повторный free был безопасен и попытка доступа легко детектировалась.Как ASan/Valgrind сообщат про нашу проблему
ASan: сообщит о heap-buffer-overflow в strcpy и/или о use-after-free при printf, выведет backtrace.Valgrind: выдаст Invalid write / Invalid read и покажет, где была выделена память и где была освобождена.Короткая шпаргалка команд
Сборка с санитайзерами:gcc -fsanitize=address,undefined -g -O1 test.c -o testЗапуск Valgrind:
valgrind --leak-check=full ./testСтатический анализ:
cppcheck --enable=all test.c
clang-tidy test.c -- параметрыпараметрыпараметры
Вывод
Главные ошибки: переполнение буфера mallocслишкоммаленькийmalloc слишком маленькийmallocслишкоммаленький, отсутствие проверки malloc, использование памяти после free. Исправляется выделением нужного размера strlen+1strlen+1strlen+1, проверкой возврата malloc, перемещением printf перед free илиудалениемобращенияпослеfreeили удалением обращения после freeилиудалениемобращенияпослеfree, использованием безопасных функций копирования и санитайзеров для отладки.