Строки в языке C
Строка — это последовательность ASCII или UNICODE символов.
Строки в C, как и в большинстве языков программирования высокого уровня рассматриваются как отдельный тип, входящий в систему базовых типов языка. Так как язык C по своему происхождению является языком системного программирования, то строковый тип данных в C как таковой отсутствует, а в качестве строк в С используются обычные массивы символов.
Исторически сложилось два представления формата строк:
- формат ANSI;
- cтроки с завершающим нулем (используется в C).
Формат ANSI устанавливает, что значением первой позиции в строке является ее длина, а затем следуют сами символы строки. Например, представление строки «Моя строка!» будет следующим:
11 ‘М’ ‘о’ ‘я’ ‘ ‘ ‘с’ ‘т’ ‘р’ ‘о’ ‘к’ ‘а’ ‘!’
В строках с завершающим нулем, значащие символы строки указываются с первой позиции, а признаком завершения строки является значение ноль. Представление рассмотренной ранее строки в этом формате имеет вид:
‘М’ ‘о’ ‘я’ ‘ ‘ ‘с’ ‘т’ ‘р’ ‘о’ ‘к’ ‘а’ ‘!’ 0
Объявление строк в C
Строки реализуются посредством массивов символов. Поэтому объявление ASCII строки имеет следующий синтаксис:
char имя[длина];
Объявление строки в С имеет тот же синтаксис, что и объявление одномерного символьного массива. Длина строки должна представлять собой целочисленное значение (в стандарте C89 – константа, в стандарте C99 может быть выражением). Длина строки указывается с учетом одного символа на хранение завершающего нуля, поэтому максимальное количество значащих символов в строке на единицу меньше ее длины. Например, строка может содержать максимально двадцать символов, если объявлена следующим образом:
char str[21]; Инициализация строки в С осуществляется при ее объявлении, используя следующий синтаксис:
char str[длина] = строковый литерал;
Строковый литерал – строка ASCII символов заключенных в двойные кавычки. Примеры объявления строк с инициализацией:
char str1[20] = «Введите значение: «, str2[20] = «»;
const char message[] = «Сообщение об ошибке!»;
Работа со строками в С
Так как строки на языке С являются массивами символов, то к любому символу строки можно обратиться по его индексу. Для этого используется синтаксис обращения к элементу массива, поэтому первый символ в строке имеет индекс ноль. Например, в следующем фрагменте программы в строке str осуществляется замена всех символов ‘a’ на символы ‘A’ и наоборот.
for(int i = 0; str[i] != 0; i++)
if (str[i] == ‘a’) str[i] = ‘A’;
else if (str[i] == ‘A’) str[i] = ‘a’;
>
Массивы строк в С
Объявление массивов строк в языке С также возможно. Для этого используются двумерные массивы символов, что имеет следующий синтаксис:
char имя[количество][длина];
Первым размером матрицы указывается количество строк в массиве, а вторым – максимальная (с учетом завершающего нуля) длина каждой строки. Например, объявление массива из пяти строк максимальной длиной 30 значащих символов будет иметь вид:
При объявлении массивов строк можно производить инициализацию:
char имя[количество][длина] = ;
Число строковых литералов должно быть меньше или равно количеству строк в массиве. Если число строковых литералов меньше размера массива, то все остальные элементы инициализируются пустыми строками. Длина каждого строкового литерала должна быть строго меньше значения длины строки (для записи завершающего нуля).
char days[12][10] = <
«Январь», «Февраль», «Март», ”Апрель», «Май»,
«Июнь», «Июль», «Август», «Сентябрь»,»Октябрь»,
«Ноябрь», «Декабрь»
>;
При объявлении массивов строк с инициализацией допускается не указывать количество строк в квадратных скобках. В таком случае, количество строк в массиве будет определено автоматически по числу инициализирующих строковых литералов.
Например, массив из семи строк:
char days[][12] = <
«Понедельник», «Вторник», «Среда», «Четверг»,
«Пятница», «Суббота», «Воскресенье»
>;
Функции для работы со строками в С
Все библиотечные функции, предназначенные для работы со строками, можно разделить на три группы:
- ввод и вывод строк;
- преобразование строк;
- обработка строк.
Ввод и вывод строк в С
Для ввода и вывода строковой информации можно использовать функции форматированного ввода и вывода (printf и scanf). Для этого в строке формата при вводе или выводе строковой переменной необходимо указать спецификатор типа %s. Например, ввод и последующий вывод строковой переменной будет иметь вид:
char str[31] = «»;
printf(«Введите строку: «);
scanf(«%30s”,str);
printf(«Вы ввели: %s”,str);
Недостатком функции scanf при вводе строковых данных является то, что символами разделителями данной функции являются:
- перевод строки,
- табуляция;
- пробел.
Поэтому, используя данную функцию невозможно ввести строку, содержащую несколько слов, разделенных пробелами или табуляциями. Например, если в предыдущей программе пользователь введет строку: «Сообщение из нескольких слов», то на экране будет выведено только «Сообщение».
Для ввода и вывода строк в библиотеке stdio.h содержатся специализированные функции gets и puts.
Функция gets предназначена для ввода строк и имеет следующий заголовок:
char * gets(char *buffer);
Между тем использовать функцию gets категорически не рекомендуется, ввиду того, что она не контролирует выход за границу строки, что может произвести к ошибкам. Вместо нее используется функция fgets с тремя параметрами:
char * fgets(char * buffer, int size, FILE * stream);
где buffer — строка для записи результата, size — максимальное количество байт, которое запишет функция fgets, stream — файловый объект для чтения данных, для чтения с клавиатуры нужно указать stdin. Эта функция читает символы со стандартного ввода, пока не считает n — 1 символ или символ конца строки, потом запишет считанные символы в строку и добавит нулевой символ. При этом функция fgets записывает в том символ конца строки в данную строку, что нужно учитывать.
Функция puts предназначена для вывода строк и имеет следующий заголовок:
int puts(const char *string);
Простейшая программа: ввод и вывод строки с использованием функций fgets и puts будет иметь вид:
char str[102] = «»;
printf(«Введите строку: «);
fgets(str, 102, stdin);
printf(«Вы ввели: «);
puts(str);
Для считывания одного символа можно использовать функцию fgetc(FILE * stream) . Она считывает один символ и возвращает значение этого символа, преобразованное к типу int, если же считывание не удалось, то возвращается специальная константа EOF, равная -1. Функция возвращает значение -1 для того, чтобы можно было обрабатывать ситуацию конца файла, посимвольное чтение до конца файла можно реализовать следующим образом:
int c;
while ((c = fgetc(stdin)) != EOF) // Обработка символа
>
Для вывода одного символа можно использовать функцию int fputc(int c, FILE *stream); .
Помимо функций ввода и вывода в потоки в библиотеке stdio.h присутствуют функции форматированного ввода и вывода в строки. Функция форматированного ввода из строки имеет следующий заголовок:
int sscanf(const char * restrict buffer, const char * restrict string, [address] . );
Функции форматированного вывода в строку имеют следующие заголовки:
int sprintf(char * restrict buffer,
const char * restrict format, [argument] . );
int snprintf(char * restrict buffer, size_t maxsize,
const char * restrict format, [argument] . );
Преобразование строк
В С для преобразования строк, содержащих числа, в численные значения в библиотеке stdlib.h
предусмотрен следующий набор функций:
double atof(const char *string); // преобразование строки в число типа double
int atoi(const char *string); // преобразование строки в число типа int
long int atol(const char *string); // преобразование строки в число типа long int
long long int atoll(const char *string); // преобразование строки в число типа long long int
Корректное представление вещественного числа в текстовой строке должно удовлетворять формату:
После символов E, e указывается порядок числа. Корректное представление целого числа в текстовой строке должно удовлетворять формату:
Помимо приведенных выше функций в библиотеке stdlib.h доступны также следующие функции преобразования строк в вещественные числа:
float strtof(const char * restrict string, char ** restrict endptr);
double strtod(const char * restrict string, char ** restrict endptr);
long double strtold(const char * restrict string,char ** restrict endptr);
Аналогичные функции присутствуют и для преобразования строк в целочисленные значения:
long int strtol(const char * restrict string, char ** restrict endptr, int base);
unsigned long strtoul(const char * restrict string,
char ** restrict endptr, int base);
long long int strtoll(const char * restrict string,
char ** restrict endptr, int base);
unsigned long long strtoull(const char * restrict string,char ** restrict endptr, int base);
Функции обратного преобразования (численные значения в строки) в библиотеке stdlib.h присутствуют, но они не регламентированы стандартом, и рассматриваться не будут. Для преобразования численных значений в строковые наиболее удобно использовать функции sprintf и snprintf.
Обработка строк
В библиотеке string.h содержаться функции для различных действий над строками.
Функция вычисления длины строки:
size_t strlen(const char *string);
char str[] = «1234»;
int n = strlen(str); //n == 4
Функции копирования строк:
char * strcpy(char * restrict dst, const char * restrict src);
char * strncpy(char * restrict dst, const char * restrict src, size_t num);
Функции сравнения строк:
int strcmp(const char *string1, const char *string2);
int strncmp(const char *string1, const char *string2,size_t num);
Функции осуществляют сравнение строк по алфавиту и возвращают:
положительное значение – если string1 больше string2;
отрицательное значение – если string1 меньше string2;
нулевое значение – если string1 совпадает с string2;
Функции объединения (конкатенации) строк:
char * strcat(char * restrict dst, const char * restrict src);
char * strncat(char * restrict dst, const char * restrict src, size_t num);
Функции поиска символа в строке:
char * strchr(const char *string, int c);
char * strrchr(const char *string, int c);
Функция поиска строки в строке:
char * strstr(const char *str, const char *substr);
char str[] = «Строка для поиска»;
char *str1 = strstr(str,»для»); //str1 == «для поиска»
Функция поиска первого символа в строке из заданного набора символов:
size_t strcspn(const char *str, const char *charset);
Функции поиска первого символа в строке не принадлежащему заданному набору символов:
size_t strspn(const char *str, const char *charset);
Функции поиска первого символа в строке из заданного набора символов:
char * strpbrk(const char *str, const char *charset);
Функция поиска следующего литерала в строке:
char * strtok(char * restrict string, const char * restrict charset);
Строки в C: как объявить и инициализировать строковые переменные в C
A Строка в C это не что иное, как набор символов в линейной последовательности. «C» всегда рассматривает строку как отдельные данные, даже если она содержит пробелы. Одиночный символ определяется с использованием одинарной кавычки. Строка представлена с помощью double кавычки.
Example, "Welcome to the world of programming!"
«C» предоставляет стандартную библиотеку который содержит множество функций, которые можно использовать для выполнения сложных operaлегко работать со строками в C.
Как объявить строку в C?
AC String — это простой массив с типом данных char. язык «С» не поддерживает напрямую строку как тип данных. Следовательно, чтобы отобразить строку в C, вам необходимо использовать массив символов.
Общий синтаксис объявления переменной как строки в C следующий:
char string_variable_name [array_size];
Классическое объявление строк можно выполнить следующим образом:
char string_name[string_length] = "string";
Размер массива должен быть определен при объявлении строковой переменной C, поскольку он используется для расчета количества символов, которые будут храниться внутри строковой переменной в C. Ниже приведены некоторые допустимые примеры объявления строк:
char first_name[15]; //declaration of a string variable char last_name[15];
В приведенном выше примере представлены строковые переменные с размером массива 15. Это означает, что данный строковый массив C способен содержать не более 15 символов. Индексация массива начинается с 0, поэтому в нем будут храниться символы с позиции 0–14. Компилятор C автоматически добавляет NULL-символ ‘\0’ в созданный массив символов.
Как инициализировать строку в C?
Давайте изучим инициализацию String в C.Follo.wing пример демонстрирует инициализацию строк в C,
char first_name[15] = "ANTHONY"; char first_name[15] = ; // NULL character '\0' is required at end in this declaration char string1 [6] = "hello";/* string size = 'h'+'e'+'l'+'l'+'o'+"NULL" = 6 */ char string2 [ ] = "world"; /* string size = 'w'+'o'+'r'+'l'+'d'+"NULL" = 6 */ char string3[6] = ; /*Declaration as set of characters ,Size 6*/
В строке 3 символ NULL должен быть добавлен явно, а символы заключены в одинарные кавычки.
«C» также позволяет нам инициализировать строковая переменная без определения размера массива символов. Это можно сделать в след.wing путь,
char first_name[ ] = "NATHAN";
Имя строки в C действует как указатель, поскольку по сути это массив.
Ввод строки C: программа C для чтения строки
При написании интерактивных программ, которые запрашивают ввод данных у пользователя, C предоставляет функции scanf(),gets() и fgets() для поиска строки текста, введенной пользователем.
Когда мы используем scanf() для чтения, мы используем спецификатор формата «%s» без использования «&» для доступа к адресу переменной, поскольку имя массива действует как указатель.
Например:
#include int main()
Вывод:
Enter your first name and age: John_Smith 48
Проблема с функцией scanf заключается в том, что она никогда не читает целые строки в C. Она останавливает процесс чтения, как только появляются пробелы, перевод страницы, вертикальная табуляция, новая строка или возврат каретки. Предположим, мы вводим входные данные как «Guru99 Tutorials», тогда функция scanf никогда не будет читать всю строку, поскольку между двумя именами встречается пробельный символ. Функция scanf будет читать только Guru99.
Чтобы прочитать строку, содержащую пробелы, мы используем функцию get(). Gets игнорирует пробелы. Это останавливает
чтение при достижении новой строки (нажата клавиша Enter).
Например:
#include int main()
Вывод:
Enter your full name: Dennis Ritchie My full name is Dennis Ritchie
Другой более безопасной альтернативой get() является функция fgets(), которая считывает указанное количество символов.
Например:
#include int main()
Вывод:
Enter your name plz: Carlos My name is Carlos
- имя строки,
- количество символов для чтения,
- stdin означает чтение со стандартного ввода, которым является клавиатура.
Вывод строки C: программа C для печати строки
Стандартная функция printf используется для печати или отображения строк на языке C на устройстве вывода. Используемый спецификатор формата: %s.
Пример,
printf("%s", name);
Вывод строк осуществляется с помощью функций fputs() и printf().
функция fputs()
Для функции fputs() требуется имя строки и указатель на то место, где вы хотите отобразить текст. Мы используем стандартный вывод, который относится к стандартному выводу для вывода на экран.
Например:
#include int main()
Вывод:
Enter your town: New York New York
помещает функцию
Функция puts используется для печати строки на языке C на устройстве вывода и перемещения курсора обратно в первую позицию. Функцию puts можно использовать ниже.wing путь,
#include int main() < char name[15]; gets(name); //reads a string puts(name); //displays a string return 0;>
Синтаксис этой функции сравнительно прост, чем у других функций.
Библиотека струн
Стандартная библиотека C предоставляет различные функции для управления строками в программе. Эти функции также называются обработчиками строк. Все эти обработчики присутствуют внутри заголовочный файл.
Функция | Цель |
---|---|
strlen () | Эта функция используется для определения длины строки. Он возвращает количество символов в строке, исключая символ NULL. |
стркат(строка1, строка2) | Эта функция используется для объединения двух строк в одну. Он добавляет или объединяет строку str2 в конец строки str1 и возвращает указатель на строку str1. |
стркмп(строка1, строка2) | Эта функция используется для сравнения двух строк друг с другом. Он возвращает 0, если строка1 равна строке2, меньше 0, если строка1 < строка2, и больше 0, если строка1 >строка2. |
Давайте рассмотрим программу ниже, которая демонстрирует функции библиотеки строк:
#include #include int main () < //string initialization char string1[15]="Hello"; char string2[15]=" World!"; char string3[15]; int val; //string comparison val= strcmp(string1,string2); if(val==0)< printf("Strings are equal\n"); >else < printf("Strings are not equal\n"); >//string concatenation printf("Concatenated string:%s",strcat(string1,string2)); //string1 contains hello world! //string length printf("\nLength of first string:%d",strlen(string1)); printf("\nLength of second string:%d",strlen(string2)); //string copy printf("\nCopied string is:%s\n",strcpy(string3,string1)); //string1 is copied into string3 return 0; >
Вывод:
Strings are not equal Concatenated string:Hello World! Length of first string:12 Length of second string:7 Copied string is:Hello World!
Другие важные функции библиотеки:
- strncmp(str1, str2, n) : возвращает 0, если первые n символов строки str1 равны первым n символам строки str2, меньше 0, если str1 < str2, и больше 0, если str1 >str2.
- strncpy(str1, str2, n) Эта функция используется для копирования строки из другой строки. Копирует первые n символов строки str2 в строку str1.
- strchr(str1, c): возвращает указатель на первое появление символа c в строке str1 или NULL, если символ не найден.
- strrchr(str1, c): он ищет строку 1 в обратном порядке и возвращает указатель на позицию символа c в строке 1 или NULL, если символ не найден.
- strstr(str1, str2): возвращает указатель на первое вхождение строки str2 в строку str1 или NULL, если строка str2 не найдена.
- strncat(str1, str2, n) Добавляет (объединяет) первые n символов строки str2 в конец строки str1 и возвращает указатель на строку str1.
- strlwr(): преобразовать строку в нижний регистр
- strupr(): преобразовать строку в верхний регистр
- strrev(): перевернуть строку
Преобразование строки в число
В программировании на C мы можем преобразовать строку числовых символов в числовое значение, чтобы предотвратить ошибку во время выполнения. Библиотека stdio.h содержит следующееwing функции для преобразования строки в число:
- int atoi(str) Обозначает целое число от ASCII; он преобразует str в эквивалентное значение int. 0 возвращается, если первый символ не является числом или нет. numbers встречаются.
- double atof(str) означает ASCII с плавающей запятой, он преобразует str в эквивалент double ценить. 0.0 возвращается, если первый символ не является числом или нет. numbers встречаются.
- long int atol(str) Заменяет ASCII на long int. Преобразует str в эквивалентное длинное целое значение. 0 возвращается, если первый символ не является числом или нет. numbers встречаются.
Фоллоwing программа демонстрирует функцию atoi():
#include int main()
Вывод:
Enter a number: 221348 you enter 221348
- Объявление указателя строки, такое как char *string = «language», является константой и не может быть изменено.
Обзор
- Строка — это последовательность символов, хранящаяся в массиве символов.
- Строка – это текст, заключенный в double кавычки.
- Такой символ, как «d», не является строкой и обозначается одинарными кавычками.
- «C» предоставляет стандартные библиотечные функции для управления строками в программе. Струнные манипуляторы хранятся в заголовочный файл.
- Строка должна быть объявлена или инициализирована перед использованием в программе.
- Существуют различные строковые функции ввода и вывода, каждая из которых имеет свои особенности.
- Не забудьте подключить библиотеку строк для работы с ее функциями.
- Мы можем преобразовать строку в число с помощью функций atoi(), atof() и atol(), которые очень полезны для процессов кодирования и декодирования.
- Мы можем манипулировать различными строками, определив массив строк в C.
- Динамическое распределение памяти в C с использованием функций malloc(), calloc()
- Приведение типов в C: преобразование типов, неявное, явное с примером
- Учебное пособие по программированию на C в формате PDF для начинающих
- 13 ЛУЧШИХ книг по программированию на C для начинающих (обновление 2024 г.)
- Разница между C и Java
- Разница между структурой и объединением в C
- 100 лучших вопросов и ответов на интервью по программированию на языке C (PDF)
- Функция calloc() в библиотеке C с ПРИМЕРОМ программы
Немного о строках в Си, или несколько вариантов оптимизировать неоптимизируемое
Не так давно у со мной произошел довольно-таки интересный инцидент, в котором был замешан один из преподавателей одного колледжа информатики.
Разговор о программировании под Linux медленно перешел к тому, что этот человек стал утверждать, что сложность системного программирования на самом деле сильно преувеличена. Что язык Си прост как спичка, собственно как и ядро Linux (с его слов).
У меня был с собой ноутбук с Linux, на котором присутствовал джентльменский набор утилит для разработки на языке Си (gcc, vim, make, valgrind, gdb). Я уже не помню, какую цель мы тогда перед собой поставили, но через пару минут мой оппонент оказался за этим ноутбуком, полностью готовый решать задачу.
И буквально на первых же строках он допустил серьезную ошибку при аллоцировании памяти под… строку.
char *str = (char *)malloc(sizeof(char) * strlen(buffer));
buffer — стековая переменная, в которую заносились данные с клавиатуры.
Я думаю, определенно найдутся люди, которые спросят: «Разве что-то тут может быть не так?».
Поверьте, может.
А что именно — читайте по катом.
Немного теории — своеобразный ЛикБез.
Если знаете — листайте до следующего хэдера.
Строка в C — это массив символов, который по-хорошему всегда должен заканчиваться ‘\0’ — символом конца строки. Строки на стеке (статичные) объявляются вот так:
char str[n] = < 0 >;
n — размер массива символов, то же, что и длина строки.
Присваивание < 0 >— «зануление» строки (опционально, объявлять можно и без него). Результат такой же, как у выполнения функций memset(str, 0, sizeof(str)) и bzero(str, sizeof(str)). Используется, чтобы в неинициализированных переменных не валялся мусор.
Так же на стеке можно сразу проинициализировать строку:
char buf[BUFSIZE] = "default buffer text\n";
Помимо этого строку можно объявить указателем и выделить под нее память на куче (heap):
char *str = malloc(size);
size — количество байт, которые мы выделяем под строку. Такие строки называются динамическими (вследствие того, что нужный размер вычисляется динамически + выделенный размер памяти можно в любой момент увеличить с помощью функции realloc() ).
В случае со стековой переменной, для определения размера массива я использовал обозначение n, в случае с переменной на куче — я использовал обозначение size. И это прекрасно отражает истинную суть отличия объявления на стеке от объявление с аллоцированием памяти на куче, ведь n как правило используется тогда, когда говорят о количестве элементов. А size — это уже совсем другая история…
Думаю. пока хватит. Идем дальше.
Нам поможет valgrind
В своей предыдущей статье я также упоминал о нем. Valgrind (раз — вики-статья, два — небольшой how-to) — очень полезная программа, которая помогает программисту отслеживать утечки памяти и ошибки контекста — как раз те вещи, которые чаще всего всплывают при работе со строками.
Давайте рассмотрим небольшой листинг, в котором реализовано что-то похожее на упомянутую мной программу, и прогоним ее через valgrind:
#include #include #include #define HELLO_STRING "Hello, Habr!\n" void main() < char *str = malloc(sizeof(char) * strlen(HELLO_STRING)); strcpy(str, HELLO_STRING); printf("->\t%s", str); free(str); >
И, собственно, результат работы программы:
[indever@localhost public]$ gcc main.c [indever@localhost public]$ ./a.out -> Hello, Habr!
Пока ничего необычного. А теперь давайте запустим эту программу с valgrind!
[indever@localhost public]$ valgrind --tool=memcheck ./a.out ==3892== Memcheck, a memory error detector ==3892== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. ==3892== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info ==3892== Command: ./a.out ==3892== ==3892== Invalid write of size 2 ==3892== at 0x4005B4: main (in /home/indever/prg/C/public/a.out) ==3892== Address 0x520004c is 12 bytes inside a block of size 13 alloc'd ==3892== at 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== by 0x400597: main (in /home/indever/prg/C/public/a.out) ==3892== ==3892== Invalid read of size 1 ==3892== at 0x4C30BC4: strlen (vg_replace_strmem.c:454) ==3892== by 0x4E89AD0: vfprintf (in /usr/lib64/libc-2.24.so) ==3892== by 0x4E90718: printf (in /usr/lib64/libc-2.24.so) ==3892== by 0x4005CF: main (in /home/indever/prg/C/public/a.out) ==3892== Address 0x520004d is 0 bytes after a block of size 13 alloc'd ==3892== at 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== by 0x400597: main (in /home/indever/prg/C/public/a.out) ==3892== -> Hello, Habr! ==3892== ==3892== HEAP SUMMARY: ==3892== in use at exit: 0 bytes in 0 blocks ==3892== total heap usage: 2 allocs, 2 frees, 1,037 bytes allocated ==3892== ==3892== All heap blocks were freed -- no leaks are possible ==3892== ==3892== For counts of detected and suppressed errors, rerun with: -v ==3892== ERROR SUMMARY: 3 errors from 2 contexts (suppressed: 0 from 0)
==3892== All heap blocks were freed — no leaks are possible — утечек нет, и это радует. Но стоит опустить глаза чуть пониже (хотя, хочу заметить, это лишь итог, основная информация немного в другом месте):
==3892== ERROR SUMMARY: 3 errors from 2 contexts (suppressed: 0 from 0)
3 ошибки. В 2х контекстах. В такой простой программе. Как!?
Да очень просто. Весь «прикол» в том, что функция strlen не учитывает символ конца строки — ‘\0’. Даже если его явно указать во входящей строке (#define HELLO_STRING «Hello, Habr!\n\0»), он будет проигнорирован.
Чуть выше результата исполнения программы, строки -> Hello, Habr! есть подробный отчет, что и где не понравилось нашему драгоценному valgrind. Предлагаю самостоятельно посмотреть эти строчки и сделать выводы.
Собственно, правильная версия программы будет выглядеть так:
#include #include #include #define HELLO_STRING "Hello, Habr!\n" void main() < char *str = malloc(sizeof(char) * (strlen(HELLO_STRING) + 1)); strcpy(str, HELLO_STRING); printf("->\t%s", str); free(str); >
Пропускаем через valgrind:
[indever@localhost public]$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==3435== ==3435== HEAP SUMMARY: ==3435== in use at exit: 0 bytes in 0 blocks ==3435== total heap usage: 2 allocs, 2 frees, 1,038 bytes allocated ==3435== ==3435== All heap blocks were freed -- no leaks are possible ==3435== ==3435== For counts of detected and suppressed errors, rerun with: -v ==3435== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Отлично. Ошибок нет, +1 байт выделяемой памяти помог решить проблему.
Что интересно, в большинстве случаев и первая и вторая программа будут работать одинаково, но если память, выделенная под строку, в которую не влез символ окончания, не была занулена, то функция printf(), при выводе такой строки, выведет и весь мусор после этой строки — будет выведено все, пока на пути printf() не встанет символ окончания строки.
Однако, знаете, (strlen(str) + 1) — такое себе решение. Перед нами встают 2 проблемы:
- А если нам надо выделить память под формируемую с помощью, например, s(n)printf(..) строку? Аргументы мы не поддерживаем.
- Внешний вид. Строка с объявлением переменной выглядит просто ужасно. Некоторые ребята к malloc еще и (char *) умудряются прикручивать, будто под плюсами пишут. В программе где регулярно требуется обрабатывать строки есть смысл найти более изящное решение.
snprintf()
int snprintf(char *str, size_t size, const char *format, . ); — функция — расширение sprintf, которая форматирует строку и записывает ее по указателю, переданному в качестве первого аргумента. От sprintf() она отличается тем, что в str не будет записано байт больше, чем указано в size.
Функция имеет одну интересную особенность — она в любом случае возвращает размер формируемой строки (без учета символа конца строки). Если строка пустая, то возвращается 0.
Одна из описанных мною проблем использования strlen связана с функциями sprintf() и snprintf(). Предположим, что нам надо что-то записать в строку str. Конечная строка содержит значения других переменных. Наша запись должна быть примерно такой:
char * str = /* тут аллоцируем память */; sprintf(str, "Hello, %s\n", "Habr!");
Встает вопрос: как определить, сколько памяти надо выделить под строку str?
char * str = malloc(sizeof(char) * (strlen(str, "Hello, %s\n", "Habr!") + 1));
— не прокатит. Прототип функции strlen() выглядит так:
#include size_t strlen(const char *s);
const char *s не подразумевает, что передаваемая в s строка может быть строкой формата с переменным количеством аргументов.
Тут нам поможет то полезное свойство функции snprintf(), о котором я говорил выше. Давайте посмотрим на код следующей программы:
#include #include #include void main() < /* Т.к. snprintf() не учитывает символ конца строки, прибавляем его размер к результату */ size_t needed_mem = snprintf(NULL, 0, "Hello, %s!\n", "Habr") + sizeof('\0'); char *str = malloc(needed_mem); snprintf(str, needed_mem, "Hello, %s!\n", "Habr"); printf("->\t%s", str); free(str); >
Запускаем программу в valgrind:
[indever@localhost public]$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==4132== ==4132== HEAP SUMMARY: ==4132== in use at exit: 0 bytes in 0 blocks ==4132== total heap usage: 2 allocs, 2 frees, 1,041 bytes allocated ==4132== ==4132== All heap blocks were freed -- no leaks are possible ==4132== ==4132== For counts of detected and suppressed errors, rerun with: -v ==4132== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) [indever@localhost public]$
Отлично. Поддержка аргументов у нас есть. Благодаря тому, что мы в качестве второго аргумента в функцию snprintf() передаем ноль, запись по нулевому указателю никогда не приведет к Seagfault. Однако, несмотря на это функция все равно вернет необходимый под строку размер.
Но с другой стороны, нам пришлось завести дополнительную переменную, да и конструкция
size_t needed_mem = snprintf(NULL, 0, "Hello, %s!\n", "Habr") + sizeof('\0');
выглядит еще хуже, чем в случае с strlen().
Вообще, + sizeof(‘\0’) можно убрать, если в конце строки формата явно указать ‘\0’ (size_t needed_mem = snprintf(NULL, 0, «Hello, %s!\n\0», «Habr»);), но это возможно отнюдь не всегда (в зависимости от механизма обработки строк мы можем выделить лишний байт).
Надо что-то сделать. Я немного подумал и решил, что сейчас настал час воззвать к мудрости древних. Опишем макрофункцию, которая будет вызывать snprintf() с нулевым указателем в качестве первого аргумента, и нулем, в качестве второго. Да и про конец строки не забудем!
#define strsize(args. ) snprintf(NULL, 0, args) + sizeof('\0')
Да, возможно, для кого-то будет новостью, но макросы в си поддерживают переменное количество аргументов, и троеточие говорит препроцессору о том, что указанному аргументу макрофункции (в нашем случае это args) соответствует несколько реальных аргументов.
Проверим наше решение на практике:
#include #include #include #define strsize(args. ) snprintf(NULL, 0, args) + sizeof('\0') void main() < char *str = malloc(strsize("Hello, %s\n", "Habr!")); sprintf(str, "Hello, %s\n", "Habr!"); printf("->\t%s", str); free(str); >
Запускаем с valgrund:
[indever@localhost public]$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==6432== ==6432== HEAP SUMMARY: ==6432== in use at exit: 0 bytes in 0 blocks ==6432== total heap usage: 2 allocs, 2 frees, 1,041 bytes allocated ==6432== ==6432== All heap blocks were freed -- no leaks are possible ==6432== ==6432== For counts of detected and suppressed errors, rerun with: -v ==6432== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Да, ошибок нет. Все корректно. И valgrind доволен, и программист наконец может пойти поспать.
Но, напоследок, скажу еще кое-что. В случае, если нам надо выделить память под какую-либо строку (даже с аргументами) есть уже полностью рабочее готовое решение.
Речь идет о функции asprintf:
#define _GNU_SOURCE /* See feature_test_macros(7) */ #include int asprintf(char **strp, const char *fmt, . );
В качестве первого аргумента она принимает указатель на строку (**strp) и аллоцирует память по разыменованному указателю.
Наша программа, написанная с использованием asprintf() будет выглядеть так:
#include #include #include void main() < char *str; asprintf(&str, "Hello, %s!\n", "Habr"); printf("->\t%s", str); free(str); >
И, собственно, в valgrind:
[indever@localhost public]$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==6674== ==6674== HEAP SUMMARY: ==6674== in use at exit: 0 bytes in 0 blocks ==6674== total heap usage: 3 allocs, 3 frees, 1,138 bytes allocated ==6674== ==6674== All heap blocks were freed -- no leaks are possible ==6674== ==6674== For counts of detected and suppressed errors, rerun with: -v ==6674== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Все отлично, но, как видите, памяти всего было выделено больше, да и alloc’ов теперь три, а не два. На слабых встраиваемых системах использование это функции нежелательно.
К тому же, если мы напишем в консоли man asprintf, то увидим:
CONFORMING TO These functions are GNU extensions, not in C or POSIX. They are also available under *BSD. The FreeBSD implementation sets strp to NULL on error.
Отсюда ясно, что данная функция доступна только в исходниках GNU.
Заключение
В заключение я хочу сказать, что работа со строками в C — это очень сложная тема, которая имеет ряд нюансов. Например, для написания «безопасного» кода при динамическом выделении памяти рекомендуется все же использовать функцию calloc() вместо malloc() — calloc забивает выделяемую память нулями. Ну или после выделения памяти использовать функцию memset(). Иначе мусор, который изначально лежал на выделяемом участке памяти, может вызвать вопросы при дебаге, а иногда и при работе со строкой.
Больше половины моих знакомых си-программистов (большинство из них — начинающие), решивших по моей просьбе задачу с выделением памяти под строки, сделали это так, что в конечном итоге это привело к ошибкам контекста. В одном случае — даже к утечке памяти (ну, забыл человек сделать free(str), с кем не бывает). Собственно говоря, это и сподвигло меня на создание сего творения, которое вы только что прочитали.
Я надеюсь, кому-то эта статья будет полезной. К чему я это все городил — никакой язык не бывает прост. Везде есть свои тонкости. И чем больше тонкостей языка вы знаете, тем лучше ваш код.
Я верю, что после прочтения этой статьи ваш код станет чуточку лучше 🙂
Удачи, Хабр!
Как создать объект из строки с в языке C
В языке C можно преобразовать строку в объект, используя различные методы. В этой статье мы рассмотрим пошаговую инструкцию по созданию объекта из строки в языке программирования C.
Шаг 1: Включите подключение необходимых заголовочных файлов. В большинстве случаев для работы с строками и объектами потребуется подключение заголовочных файлов «string.h» и «stdlib.h». Это позволит вам использовать функции для работы со строками и выделения памяти для объектов.
Шаг 2: Объявите переменные для строки и объекта. Создайте строку, которую вы хотите преобразовать в объект, и объявите переменную для объекта, в который будет сохраняться результат преобразования. Не забудьте выделить память для объекта.
Шаг 3: Используйте функции для преобразования строки в объект. В языке C есть несколько функций, которые позволяют преобразовывать строку в объект определенного типа. Например, функция «atoi» преобразует строку в целое число, функция «atof» преобразует строку в число с плавающей точкой, а функция «strcpy» копирует строку в другую строку. Выберите подходящую функцию в зависимости от типа объекта, в который вы хотите преобразовать строку.
Шаг 4: Освободите память и обработайте результат преобразования. После преобразования строки в объект, у вас может возникнуть необходимость освободить выделенную память, а также обработать полученный результат. Проверьте, что преобразование прошло успешно, и выполните необходимые операции с объектом.
Используя эту пошаговую инструкцию, вы сможете создать объект из строки в языке C и использовать его в своей программе. Следуйте указанным шагам и преобразуйте строку в объект нужного вам типа. Удачи в программировании!