﻿# Начало коллекционирования ошибок в функциях копирования

Уже несколько раз замечал, что программисты допускают ошибки в простых функциях копирования данных\. Эта тема потребует в будущем ещё много времени для изучения и подбора материала, чтобы написать основательную статью\. Но захотелось поделиться парой примеров, недавно замеченных мною\.

![0723_copy_functions_ru/image1.png](https://import.viva64.com/docx/blog/0723_copy_functions_ru/image1.png)

## Феномен Баадера\-Майнхоф? Нет, не думаю

Как член команды PVS\-Studio я сталкиваюсь с большим количеством ошибок, обнаруживаемых нами в различных проектах\. Как DevRel \- люблю про это рассказывать :\)\. Сегодня я решил поговорить про неправильно реализованные функции копирования данных\.

Такие неудачные функции попадались мне уже не раз\. Но я их не выписывал, так как не придавал этому значения\. Однако раз я заметил такую тенденцию, пора начинать их коллекционировать\. Для начала поделюсь двумя последними замеченными случаями\.

Кто\-то может возразить, что два случая \- это ещё не закономерность\. И что, возможно, я обратил на них внимание исключительно из\-за того, что они встретились мне через небольшое количество времени и сработал феномен [Баадера\-Майнхоф](https://ru.wikipedia.org/wiki/%D0%A4%D0%B5%D0%BD%D0%BE%D0%BC%D0%B5%D0%BD_%D0%91%D0%B0%D0%B0%D0%B4%D0%B5%D1%80%D0%B0_%E2%80%94_%D0%9C%D0%B0%D0%B9%D0%BD%D1%85%D0%BE%D1%84)\.

_Феномен Баадера\-Майнхоф \(англ\. the Baader\-Meinhof phenomenon\), также иллюзия частотности \- это когнитивное искажение, при котором недавно узнанная информация, появляющаяся вновь спустя непродолжительный период времени, воспринимается как необычайно часто повторяющаяся\._

Думаю, что это не так\. У меня уже был опыт подобного наблюдения про функции сравнения, который затем подтверждался собранным материалом: "[Зло живёт в функциях сравнения](https://pvs-studio.ru/ru/blog/posts/cpp/0509/)"\.

Ладно, перейдём к сути\. Вступление для того, чтобы пока привести всего два примера, слишком затянулось :\)\.

## Пример N1

В [статье](https://pvs-studio.ru/ru/blog/posts/0721/) про проверку Zephyr RTOS я описал вот такую неудачную попытку реализации аналога функции _strdup_:

```cpp
static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    ((u8_t *)mntpt)[strlen(mntpt)] = '\0';
    memcpy(cpy_mntpt, mntpt, strlen(mntpt));
  }
  return cpy_mntpt;
}
```

Предупреждение PVS\-Studio: [V575](https://pvs-studio.ru/ru/docs/warnings/v575/) \[CWE\-628\] The 'memcpy' function doesn't copy the whole string\. Use 'strcpy / strcpy\_s' function to preserve terminal null\. shell\.c 427

Анализатор сообщает, что функция _memcpy_ копирует строчку, но не скопирует терминальный ноль, и это очень подозрительно\. Кажется, что этот терминальный 0 копируется здесь:

```cpp
((u8_t *)mntpt)[strlen(mntpt)] = '\0';
```

Нет, здесь опечатка, из\-за которой терминальный ноль копируется сам в себя\. Обратите внимание, что запись происходит в массив _mntpt_, а не в _cpy\_mntpt_\. В итоге функция _mntpt\_prepare_ возвращает строку, незавершенную терминальным нулём\.

На самом деле, программист хотел написать так:

```cpp
((u8_t *)cpy_mntpt)[strlen(mntpt)] = '\0';
```

Непонятно только, зачем код написан так запутанно и нестандартно\. Как результат, в небольшой и несложной функции допущена серьезная ошибка\. Этот код можно упростить до следующего варианта:

```cpp
static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    strcpy(cpy_mntpt, mntpt);
  }
  return cpy_mntpt;
}
```

## Пример N2

```cpp
void myMemCpy(void *dest, void *src, size_t n) 
{ 
   char *csrc = (char *)src; 
   char *cdest = (char *)dest; 
   for (int i=0; i<n; i++) 
     cdest[i] = csrc[i]; 
}
```

Этот код не мы сами выявили с помощью PVS\-Studio, а я случайно встретил его на сайте Stack Overflow: [C and static Code analysis: Is this safer than memcpy?](https://stackoverflow.com/questions/55239222/c-and-static-code-analysis-is-this-safer-than-memcpy)

Впрочем, если проверить эту функцию с помощью анализатора PVS\-Studio, он справедливо заметит:

* [V104](https://pvs-studio.ru/ru/docs/warnings/v104/) Implicit conversion of 'i' to memsize type in an arithmetic expression: i < n test\.cpp 26
* [V108](https://pvs-studio.ru/ru/docs/warnings/v108/) Incorrect index type: cdest\[not a memsize\-type\]\. Use memsize type instead\. test\.cpp 27
* [V108](https://pvs-studio.ru/ru/docs/warnings/v108/) Incorrect index type: csrc\[not a memsize\-type\]\. Use memsize type instead\. test\.cpp 27

И действительно, этот код содержит недостаток, про который указали и в ответах на Stack Overflow\. Нельзя использовать в качестве индекса переменную типа _int_\. В 64\-битной программе, почти наверняка \(экзотические архитектуры не рассматриваем\), переменная _int_ будет 32\-битной и функция сможет скопировать не более INT\_MAX байт\. Т\.е\. не более 2 Гигабайт\.

При большем размере копируемого буфера произойдёт переполнение знаковой переменной, что с точки зрения языка C и C\+\+ является неопределённым поведением\. И, кстати, не старайтесь угадать, как именно проявит себя ошибка\. Это на самом деле непростая тема, про которую можно прочитать в статье "[Undefined behavior ближе, чем вы думаете](https://pvs-studio.ru/ru/blog/posts/cpp/0374/)"\.

Особенно забавно, что этот код появился как попытка убрать какое\-то предупреждение анализатора Checkmarx, возникавшее при вызове функции _memcpy_\. Программист не придумал ничего лучше, как сделать свой собственный велосипед\. И несмотря на простоту функции копирования, она всё равно получилась неправильной\. То есть по факту человек, скорее всего, сделал ещё хуже, чем было\. Вместо того, чтобы разобраться в причине предупреждения, он маскировал проблему написанием своей собственной функции \(запутал анализатор\)\. Плюс добавил ошибку, используя для счётчика _int_\. Ах да, такой код ещё может помешать оптимизации\.  Неэффективно использовать свой собственный код вместо эффективной оптимизированной функции _memcpy_\. Не делайте так :\)

## Заключение

Что же, я только в начале пути и, возможно, пройдёт не один год, прежде чем я накоплю материалов для основательной публикации по этой теме\. Собственно, только теперь я начну выписывать подобные случаи\. Спасибо за внимание и посмотрите, что интересного найдёт анализатор [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) в вашем C/C\+\+/C\#/Java коде\.