Рассмотрите C-функцию: char* concat(const char* a, const char* b) { char* r = malloc(strlen(a) + strlen(b)); strcpy(r, a); strcat(r, b); return r; } Найдите и объясните ошибки и предложите безопасную реализацию с учётом границ и выделения памяти
Ошибки и недостатки оригинальной реализации - Не учитывается место для завершающего нулевого символа: выделяется strlen(a)+strlen(b)\text{strlen}(a)+\text{strlen}(b)strlen(a)+strlen(b) байт вместо strlen(a)+strlen(b)+1\text{strlen}(a)+\text{strlen}(b)+1strlen(a)+strlen(b)+1. Это приводит к переполнению буфера при записи '\0'. - Не проверяется возвращаемое значение от malloc — возможен NULL при недостатке памяти. - Не проверяются входные указатели aaa и bbb — разыменование NULL вызовет UB. - strcpy/strcat небезопасны, они не проверяют границы буфера. - Не проверяется переполнение целочисленного суммирования длин (overflow при очень больших строках). Безопасная реализация (коротко, с проверками) ``` #include
#include
#include /* для SIZE_MAX */ char *concat(const char *a, const char *b) { if (!a) a = ""; if (!b) b = ""; size_t la = strlen(a); size_t lb = strlen(b); /* проверка переполнения: нужно выделить la + lb + 1 байт */ if (la > SIZE_MAX - lb - 1) return NULL; /* защита от overflow */ size_t total = la + lb + 1; char *r = malloc(total); if (!r) return NULL; /* безопасное копирование без вызова strcat */ memcpy(r, a, la); memcpy(r + la, b, lb); r[la + lb] = '\0'; return r; } ``` Замечания: - Мы выделяем la+lb+1\text{la} + \text{lb} + 1la+lb+1 байт и явно записываем терминатор `'\0'`. - Проверяем возврат `malloc` и переполнение длин (la>SIZE_MAX−lb−1la > SIZE\_MAX - lb - 1la>SIZE_MAX−lb−1). - Вариант с `snprintf(r, total, "%s%s", a, b)` также безопасен для завершающего нуля. - Освобождение памяти — задача вызывающего (`free()`). - Если хотите считать NULL входы ошибкой, можно возвращать NULL или использовать иной контракт; приведённый код трактует NULL как пустую строку.
- Не учитывается место для завершающего нулевого символа: выделяется strlen(a)+strlen(b)\text{strlen}(a)+\text{strlen}(b)strlen(a)+strlen(b) байт вместо strlen(a)+strlen(b)+1\text{strlen}(a)+\text{strlen}(b)+1strlen(a)+strlen(b)+1. Это приводит к переполнению буфера при записи '\0'.
- Не проверяется возвращаемое значение от malloc — возможен NULL при недостатке памяти.
- Не проверяются входные указатели aaa и bbb — разыменование NULL вызовет UB.
- strcpy/strcat небезопасны, они не проверяют границы буфера.
- Не проверяется переполнение целочисленного суммирования длин (overflow при очень больших строках).
Безопасная реализация (коротко, с проверками)
```
#include #include #include /* для SIZE_MAX */
char *concat(const char *a, const char *b) {
if (!a) a = "";
if (!b) b = "";
size_t la = strlen(a);
size_t lb = strlen(b);
/* проверка переполнения: нужно выделить la + lb + 1 байт */
if (la > SIZE_MAX - lb - 1) return NULL; /* защита от overflow */
size_t total = la + lb + 1;
char *r = malloc(total);
if (!r) return NULL;
/* безопасное копирование без вызова strcat */
memcpy(r, a, la);
memcpy(r + la, b, lb);
r[la + lb] = '\0';
return r;
}
```
Замечания:
- Мы выделяем la+lb+1\text{la} + \text{lb} + 1la+lb+1 байт и явно записываем терминатор `'\0'`.
- Проверяем возврат `malloc` и переполнение длин (la>SIZE_MAX−lb−1la > SIZE\_MAX - lb - 1la>SIZE_MAX−lb−1).
- Вариант с `snprintf(r, total, "%s%s", a, b)` также безопасен для завершающего нуля.
- Освобождение памяти — задача вызывающего (`free()`).
- Если хотите считать NULL входы ошибкой, можно возвращать NULL или использовать иной контракт; приведённый код трактует NULL как пустую строку.