Проанализируйте следующий фрагмент на C и объясните, в чём ошибка, какие последствия возможны и как её исправить: char *f() { char buf[10]; strcpy(buf, "hello world"); return buf; }
Ошибка в двух местах: 1) Переполнение буфера: строка `"hello world"` занимает 111111 символов плюс завершающий `'\0'` — всего 121212 байт, а массив объявлен как `char buf[10]`. При выполнении `strcpy(buf, "hello world")` будет записано как минимум 12−10=212-10=212−10=2 лишних байта за пределы буфера — неопределённое поведение и возможная порча памяти (коррупция стека, перезапись соседних переменных, тревожные последствия для безопасности). 2) Возврат указателя на локальный автоматический массив: `buf` хранится в стеке функции; после возврата его область перестаёт быть валидной, поэтому возвращаемый указатель указывает на невалидную память — также неопределённое поведение (use-after-return). Возможные последствия - Немедленный крах (segfault) или неверные данные при обращении к возвращённому указателю. - Коррупция стека, нарушение контроля потока (return address) — возможна уязвимость для выполнения произвольного кода. - Трудноуловимые баги, зависимые от компилятора/оптимизаций/платформы. Как исправить (варианты, выбрать подходящий по семантике): 1) Вернуть указатель на статическую строку (простое, но не потокобезопасно, не переиспользуемо): char *f() { return "hello world"; /* строковый литерал в статической памяти */ } 2) Выделить память динамически и вернуть её (вызов должен освободить): char *f() { size_t n = strlen("hello world") + 1; /* n = 12 */ char *p = malloc(n); if (!p) return NULL; strcpy(p, "hello world"); return p; /* caller должен free(p) */ } 3) Пусть вызывающий предоставляет буфер: void f(char *buf, size_t bufsz) { /* обеспечить, что буfsz >= strlen(...) + 1 */ if (bufsz > 0) snprintf(buf, bufsz, "%s", "hello world"); } 4) Использовать static внутри функции (состояние сохраняется между вызовами): char *f() { static char buf[12]; strcpy(buf, "hello world"); return buf; } (иметь в виду: не потокобезопасно, перезаписывается при каждом вызове) 5) Использовать безопасные копии с контролем длины: char *f() { char *p = malloc(12); if (!p) return NULL; /* или strlcpy(p, "hello world", 12) если доступна */ snprintf(p, 12, "%s", "hello world"); return p; } Главное — либо обеспечить буфер достаточного размера (≥12 \ge 12 ≥12 в этом примере) и корректную область жизни, либо не возвращать указатель на автоматическую память.
1) Переполнение буфера: строка `"hello world"` занимает 111111 символов плюс завершающий `'\0'` — всего 121212 байт, а массив объявлен как `char buf[10]`. При выполнении `strcpy(buf, "hello world")` будет записано как минимум 12−10=212-10=212−10=2 лишних байта за пределы буфера — неопределённое поведение и возможная порча памяти (коррупция стека, перезапись соседних переменных, тревожные последствия для безопасности).
2) Возврат указателя на локальный автоматический массив: `buf` хранится в стеке функции; после возврата его область перестаёт быть валидной, поэтому возвращаемый указатель указывает на невалидную память — также неопределённое поведение (use-after-return).
Возможные последствия
- Немедленный крах (segfault) или неверные данные при обращении к возвращённому указателю.
- Коррупция стека, нарушение контроля потока (return address) — возможна уязвимость для выполнения произвольного кода.
- Трудноуловимые баги, зависимые от компилятора/оптимизаций/платформы.
Как исправить (варианты, выбрать подходящий по семантике):
1) Вернуть указатель на статическую строку (простое, но не потокобезопасно, не переиспользуемо):
char *f() {
return "hello world"; /* строковый литерал в статической памяти */
}
2) Выделить память динамически и вернуть её (вызов должен освободить):
char *f() {
size_t n = strlen("hello world") + 1; /* n = 12 */
char *p = malloc(n);
if (!p) return NULL;
strcpy(p, "hello world");
return p; /* caller должен free(p) */
}
3) Пусть вызывающий предоставляет буфер:
void f(char *buf, size_t bufsz) {
/* обеспечить, что буfsz >= strlen(...) + 1 */
if (bufsz > 0)
snprintf(buf, bufsz, "%s", "hello world");
}
4) Использовать static внутри функции (состояние сохраняется между вызовами):
char *f() {
static char buf[12];
strcpy(buf, "hello world");
return buf;
}
(иметь в виду: не потокобезопасно, перезаписывается при каждом вызове)
5) Использовать безопасные копии с контролем длины:
char *f() {
char *p = malloc(12);
if (!p) return NULL;
/* или strlcpy(p, "hello world", 12) если доступна */
snprintf(p, 12, "%s", "hello world");
return p;
}
Главное — либо обеспечить буфер достаточного размера (≥12 \ge 12 ≥12 в этом примере) и корректную область жизни, либо не возвращать указатель на автоматическую память.