Строковые функции strlen(), strcpy(), strncpy(), strcat(), strncat()

Практический курс по C/C++: https://stepik.org/course/193691

С этого занятия мы начнем рассмотрение основных функций для работы со строками. Первое, с чем приходится иметь дело – это копирование одной строки в другую, а, точнее, из одного массива в другой. Давайте посмотрим, как это можно сделать.

Пусть у нас объявлены две строки (два байтовых массива) разного размера:

#include <stdio.h>
 
int main(void) 
{
         char source[100] = "Source string";
         char destination[10];
         
         return 0;
}

Наша задача скопировать содержимое строки source (источник) в строку destination (назначение). Первое, что, почему то пробуют делать начинающие кодеры, это просто присвоить один массив другому:

destination = source;   // ошибка

Но мы уже знаем, что массивы так нельзя присваивать, т.к. это неизменяемые указатели на области памяти. Единственный вариант – это перебрать элементы первого массива и посимвольно скопировать во второй. Для этого, очевидно, нужно воспользоваться оператором цикла. Я реализую этот алгоритм с помощью цикла while следующим образом:

#include <stdio.h>
 
int main(void) 
{
         char source[100] = "Source string";
         char destination[10];
 
         const char* src = source;
         char* dst = destination;
         int max_len_copy = sizeof(destination);
 
         while(*src != '\0' && max_len_copy-- > 1)
                   *dst++ = *src++;
         *dst = '\0';
 
         puts(destination);
         
         return 0;
}

Вначале идет объявление вспомогательных указателей src и dst, причем указатель src определен с ключевым словом const, показывающий, что исходная строка изменена через указатель src не будет. Далее, объявлена переменная max_len_copy со значением размера массива destination, в который будет выполняться копирование строки. В цикле мы будем копировать символы, пока либо не дойдем до конца строки source, либо не скопируем max_len_copy-1 символ, чтобы не выйти за пределы массива destination. После цикла в конец копируемой строки необходимо прописать символ ее конца ‘\0’.

После выполнения программы увидим фрагмент строки из 9 символов:

Source st

Если же размер массива destination увеличить до 20 символов:

char destination[10];

то будет скопирована вся строка:

Source string

Функция strcpy()

Конечно, каждый раз писать цикл, чтобы скопировать одну строку в другую, очень неудобно. Поэтому в языке Си уже есть стандартные функции, которые выполняют эту операцию:

char* strcpy(char* dest, const char* src);
char* strncpy(char* dest, const char* src, int max_len);

Обе они определены в заголовочном файле string.h и возвращают указатели dest. Первая функция strcpy() в dest копирует все символы строки из src, пока не встретится символ конца строки (он тоже копируется). Вторая функция делает то же самое, но дополнительно еще проверяет, чтобы максимальное количество копируемых символов не превышало значения max_len. Такой подход считается более безопасным.

Давайте на примерах посмотрим, как их можно использовать:

#include <stdio.h>
#include <string.h>
 
int main(void) 
{
         char s_1[100] = "Source string";
         char d_1[10], d_2[20];
 
         strcpy(d_1, s_1);
         strcpy(d_2, s_1);
 
         puts(d_1);
         puts(d_2);
 
         return 0;
}

Смотрите, здесь длина массива d_1 недостаточна для хранения строки s_1. Поэтому после запуска программы получаем неожиданный результат содержимое массивов d_1 и d_2:

Source string
ing

Если же массив d_1 увеличить, например, до 20 элементов, то проблема исчезнет, и мы получим корректный результат копирования:

Source string
Source string

Как раз для контроля за этой уязвимостью, лучше использовать вторую функцию strncpy() следующим образом:

#include <stdio.h>
#include <string.h>
 
int main(void) 
{
         char s_1[100] = "Source string";
         char d_1[10], d_2[20];
 
         int max_len = sizeof(d_1)-1;
         strncpy(d_1, s_1, max_len);
         d_1[max_len] = 0;
         strncpy(d_2, "Balakirev", 5);
         d_2[5] = 0;
 
         puts(d_1);
         puts(d_2);
 
         return 0;
}

Обратите внимание, функция strncpy() не добавляет автоматически символ ‘\0’ при окончании процесса копирования. Если в строке встретился этот символ, то он будет записан в новую строку, но если мы достигли предела max_len и символа ‘\0’ при этом не было, то новой строке его также не будет. Именно поэтому, для гарантии, мы его прописываем в последнюю позицию. Также в качестве копируемой строки можно прописывать строковый литерал. При выполнении функции strncpy() вместо него будет подставлен адрес строки "Balakirev" и далее процесс копирования будет происходить так же, как и в случае с массивами.

Функция strlen() определения длины строки

Следующая часто используемая операция – это определение длины строки, то есть, числа символов. О чем здесь речь? Давайте предположим, что в программе объявлена строка:

char str[100] = "Length of the string";

Спрашивается, чему равна ее длина? Правильный ответ будет 20 – число символов до символа конца строки:

Не нужно путать длину строки с числом элементов массива. В данном примере, массив str содержит 100 элементов, но длина строки равна 20.

Как же вычислить эту длину? Здесь опять же следует воспользоваться циклом, например, так:

#include <stdio.h>
 
int main(void) 
{
         char str[100] = "Length of the string";
 
         const char *buf = str;
         size_t length = 0;
         while(*buf++)
                   length++;
 
         printf("length = %zu\n", length);
 
         return 0;
}

Мы здесь объявили вспомогательный указатель buf на строку str и переменную length типа size_t – целый беззнаковый тип. Затем, идет цикл while, который будет выполняться пока истинно условие, то есть, пока код текущего символа не равен нулю. А ноль, как мы знаем, это маркер конца строки. В цикле на каждой итерации происходит увеличение переменной length на единицу. В результате, когда встретится число 0, переменная length будет содержать значение длины строки.

Опять же, каждый раз прописывать цикл для определения длины строки, было бы очень неудобно. Поэтому в заголовочном файле string.h имеется определение следующей функции:

size_t strlen(const char* buf);

которая на входе принимает указатель на строку и возвращает ее длину.

Функция strlen() перебирает ячейки памяти, начиная с указанного адреса, пока не будет встречено значение 0. Если переданная строка корректна, то есть, содержит символ конца строки, то проблем с вычислением длины не возникает. Например:

size_t len = strlen(str);

Однако если в конце строки по каким-либо причинам не будет символа ‘\0’, то цикл пойдет дальше по ячейкам памяти, вполне может выйти за пределы массива и остановиться в произвольной позиции. Программа от этого аварийно не завершится, т.к. данные лишь читаются из ячеек памяти, но возвращенное значение длины явно будет неверным. Чтобы такой ситуации не возникало, передавайте в функцию strlen() только корректные строки.

Функции strcat() и strncat() объединения двух строк

Следующие две полезные функции – это объединение двух строк, а точнее, добавление к первой строке dest второй src:

char* strcat(char* dest, const char* src);
char* strncat(char* dest, const char* src, int max_add);

Первая функция добавляет все символы строки src к концу строки dest (включая символ конца строки), а вторая делает то же самое, но с дополнительным ограничением добавляемых символов не более max_add. Возвращают обе функции указатель на первую строку (результат объединения).

Вот пример использования этих функций:

#include <stdio.h>
#include <string.h>
 
int main(void) 
{
         char str_cat[100] = "Sergey";
         char str[15] = "Balalkirev";
 
         strcat(str_cat, str);
         printf("%s", str_cat);
 
         printf("\n");
 
         size_t max_add = sizeof(str) - strlen(str) - 1;
         strncat(str, str_cat, max_add);
         str[sizeof(str) - 1] = '\0';
         printf("%s", str);
 
         return 0;
}

Первая функция strcat() добавляет в конец строки str_cat строку str, а вторая strncat(), наоборот, ко второй строке str первую str_cat с максимальным ограничением в max_add элементов (символов).

После запуска программы увидим следующий результат их работы:

SergeyBalalkirev
BalalkirevSerg

Как видим, обе функции сформировали корректный результат. Причем, для второй функции максимальное число добавляемых символов было вычислено как разница между всеми элементами массива и числом уже занятых элементов минус один:

size_t max_add = sizeof(str) - strlen(str) - 1;

Этот минус один нужен, чтобы сформировать корректную строку, если при объединении строк будет достигнут рубеж в max_add символов. Функция strncat() не добавляет автоматом символ конца строки, поэтому мы это должны сделать сами.

Конечно, на практике предпочтение следует отдавать второй функции strncat(), так как она более безопасна в использовании и позволяет контролировать выход за пределы массива, куда выполняется добавление новых значений. А первую функцию strcat() удобно применять при добавлении к строке какого-либо строкового литерала, где мы легко можем контролировать итоговую длину объединенной строки.

Практический курс по C/C++: https://stepik.org/course/193691

Видео по теме