Как стирать строки в си

Объявление строк

Строка в языке Си представляет собой одномерный массив символов, последним элементом которой является символ конца строки – нуль (строка, завершающаяся нулем, то есть NULL terminated string).

Объявление переменной типа строка в языке Си возможно тремя способами, два из которых инициализируют строку во время объявления.

Первый способ:

Объявления массива символов (не забудьте добавить место для завершающего нуля):

char s[40+1];

Второй способ:

Присвоить строковой переменной начальное значение (при этом длину строки компилятор может вычислить сам):

char s[] = «Пример инициализации строки»;

Справа от знака присваивания записана строковая константа. В конце строки автоматически добавляется ноль (‘’). Константы символьных строк помещаются в класс статической памяти.

Третий способ:

Неявное указание, что используется массив. В левой части от знака присваивания указывается указатель на символ:

char *s=»Второй вариант инициализации»;

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

char *s;

Ввод строки со стандартного устройства ввода (клавиатуры)

Для работы со строками есть набор функций. Для ввода со стандартного устройства ввода (клавиатуры) чаще всего используются библиотечные функциями из модуля стандартного ввода-вывода: scanf и gets.

Для ввода строки с помощью функции scanf, использует формат «%s», причем обратите внимание на то, что перед идентификатором строки не используется знак адреса «&», так как одномерный массив уже представлен указателем на его начало:

scanf(«%s», s);

Функция gets() считывает символы до тех пор, пока не достигнет символа перехода на новую строку. Функция принимает все символы вплоть до символа перевода строки, но не включает его. К концу строки добавляется завершающий ноль (‘’). Функция gets() помещает считанную с клавиатуры последовательность символов в параметр типа строка и возвращает указатель на эту строку (если операция завершилась успешно), или NULL (в случае ошибки). В приведенном ниже примере при успешном завершении операции, на экран будет выведено две одинаковые строки:

#include <stdio.h>
int main()
{ char s[50];
char *p;
p=gets(s);
printf(» n Введена строка %s. «,s);
if (p) printf(» n Введена строка %s. «,p);
return 0;
}

Попутно заметим, что функция gets часто используется для ввода лю-бых данных с клавиатуры в виде строки с целью дальнейшего преобразования функцией sscanf к нужному формату или для предварительного анализа вводимых данных, например:

#include <string.h>
#include <stdio.h>
#include <conio.h>
int main()
{ char s[50]; int x, err;
do
{ printf(» n Введите целое число -> «);
gets(s);
err=sscanf(s, «%d»,&x);
if (err!=1) printf(» n Ошибка ввода. «);
} while (err!=1);
printf(«n Введено целое число -> %d», x);
return 0;
}

Вывод строк на стандартное устройство вывода (экран монитора)

Для вывода строк на стандартное устройство вывода (экран монитора) можно использовать две функции printf и puts. В функции printf в качестве формата передается «%s». Удобство использования этой функции заключается в том, что помимо строки можно сразу выводит данные других типов. Особенность функции puts заключается в том, что после вывода строки автоматически происходит переход на следующую строку.

Функции для работы со строками

Для преобразования строк в языке Си предусмотрена библиотека string. Каждая из функций имеет свой формат записи (прототип).

Наиболее  используемые функции рассмотрены в этой статье. — читать

Пример программ(листинг) работающей со строками

Пример №1(листинг) | Пример№2 (Листинг)

Источник

Хабра, привет!

Не так давно у со мной произошел довольно-таки интересный инцидент, в котором был замешан один из преподавателей одного колледжа информатики.

Разговор о программировании под Linux медленно перешел к тому, что этот человек стал утверждать, что сложность системного программирования на самом деле сильно преувеличена. Что язык Си прост как спичка, собственно как и ядро Linux (с его слов).

У меня был с собой ноутбук с Linux, на котором присутствовал джентльменский набор утилит для разработки на языке Си (gcc, vim, make, valgrind, gdb). Я уже не помню, какую цель мы тогда перед собой поставили, но через пару минут мой оппонент оказался за этим ноутбуком, полностью готовый решать задачу.

И буквально на первых же строках он допустил серьезную ошибку при аллоцировании памяти под… строку.

char *str = (char *)malloc(sizeof(char) * strlen(buffer));

buffer — стековая переменная, в которую заносились данные с клавиатуры.

Я думаю, определенно найдутся люди, которые спросят: «Разве что-то тут может быть не так?».
Поверьте, может.

А что именно — читайте по катом.

Немного теории — своеобразный ЛикБез.

Если знаете — листайте до следующего хэдера.

Строка в C — это массив символов, который по-хорошему всегда должен заканчиваться ‘’ — символом конца строки. Строки на стеке (статичные) объявляются вот так:

char str[n] = { 0 };

n — размер массива символов, то же, что и длина строки.

Присваивание { 0 } — «зануление» строки (опционально, объявлять можно и без него). Результат такой же, как у выполнения функций memset(str, 0, sizeof(str)) и bzero(str, sizeof(str)). Используется, чтобы в неинициализированных переменных не валялся мусор.

Так же на стеке можно сразу проинициализировать строку:

char buf[BUFSIZE] = «default buffer textn»;

Помимо этого строку можно объявить указателем и выделить под нее память на куче (heap):

char *str = malloc(size);

size — количество байт, которые мы выделяем под строку. Такие строки называются динамическими (вследствие того, что нужный размер вычисляется динамически + выделенный размер памяти можно в любой момент увеличить с помощью функции realloc() ).

В случае со стековой переменной, для определения размера массива я использовал обозначение n, в случае с переменной на куче — я использовал обозначение size. И это прекрасно отражает истинную суть отличия объявления на стеке от объявление с аллоцированием памяти на куче, ведь n как правило используется тогда, когда говорят о количестве элементов. А size — это уже совсем другая история…

Думаю. пока хватит. Идем дальше.

Нам поможет valgrind

В своей предыдущей статье я также упоминал о нем. Valgrind (раз — вики-статья, два — небольшой how-to) — очень полезная программа, которая помогает программисту отслеживать утечки памяти и ошибки контекста — как раз те вещи, которые чаще всего всплывают при работе со строками.

Давайте рассмотрим небольшой листинг, в котором реализовано что-то похожее на упомянутую мной программу, и прогоним ее через valgrind:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#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 не учитывает символ конца строки — ‘’. Даже если его явно указать во входящей строке (#define HELLO_STRING «Hello, Habr!n»), он будет проигнорирован.

Чуть выше результата исполнения программы, строки -> Hello, Habr! есть подробный отчет, что и где не понравилось нашему драгоценному valgrind. Предлагаю самостоятельно посмотреть эти строчки и сделать выводы.

Собственно, правильная версия программы будет выглядеть так:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#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 проблемы:

  1. А если нам надо выделить память под формируемую с помощью, например, s(n)printf(..) строку? Аргументы мы не поддерживаем.
  2. Внешний вид. Строка с объявлением переменной выглядит просто ужасно. Некоторые ребята к malloc еще и (char *) умудряются прикручивать, будто под плюсами пишут. В программе где регулярно требуется обрабатывать строки есть смысл найти более изящное решение.

Давайте придумаем такое решение, которое удовлетворит и нас, и valgrind.

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, %sn», «Habr!»);

Встает вопрос: как определить, сколько памяти надо выделить под строку str?

char * str = malloc(sizeof(char) * (strlen(str, «Hello, %sn», «Habr!») + 1));

— не прокатит. Прототип функции strlen() выглядит так:

#include <string.h>
size_t strlen(const char *s);

const char *s не подразумевает, что передаваемая в s строка может быть строкой формата с переменным количеством аргументов.

Тут нам поможет то полезное свойство функции snprintf(), о котором я говорил выше. Давайте посмотрим на код следующей программы:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main() {
/* Т.к. snprintf() не учитывает символ конца строки, прибавляем его размер к результату */
size_t needed_mem = snprintf(NULL, 0, «Hello, %s!n», «Habr») + sizeof(‘’);
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(‘’);

выглядит еще хуже, чем в случае с strlen().

Вообще, + sizeof(‘’) можно убрать, если в конце строки формата явно указать ‘’ (size_t needed_mem = snprintf(NULL, 0, «Hello, %s!n», «Habr»);), но это возможно отнюдь не всегда (в зависимости от механизма обработки строк мы можем выделить лишний байт).

Надо что-то сделать. Я немного подумал и решил, что сейчас настал час воззвать к мудрости древних. Опишем макрофункцию, которая будет вызывать snprintf() с нулевым указателем в качестве первого аргумента, и нулем, в качестве второго. Да и про конец строки не забудем!

#define strsize(args…) snprintf(NULL, 0, args) + sizeof(‘’)

Да, возможно, для кого-то будет новостью, но макросы в си поддерживают переменное количество аргументов, и троеточие говорит препроцессору о том, что указанному аргументу макрофункции (в нашем случае это args) соответствует несколько реальных аргументов.

Проверим наше решение на практике:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define strsize(args…) snprintf(NULL, 0, args) + sizeof(‘’)

void main() {
char *str = malloc(strsize(«Hello, %sn», «Habr!»));
sprintf(str, «Hello, %sn», «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 <stdio.h>

int asprintf(char **strp, const char *fmt, …);

В качестве первого аргумента она принимает указатель на строку (**strp) и аллоцирует память по разыменованному указателю.

Наша программа, написанная с использованием asprintf() будет выглядеть так:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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), с кем не бывает). Собственно говоря, это и сподвигло меня на создание сего творения, которое вы только что прочитали.

Я надеюсь, кому-то эта статья будет полезной. К чему я это все городил — никакой язык не бывает прост. Везде есть свои тонкости. И чем больше тонкостей языка вы знаете, тем лучше ваш код.

Я верю, что после прочтения этой статьи ваш код станет чуточку лучше 🙂
Удачи, Хабр!

Источник

В этом уроке мы с вами будем обсуждать строки в стиле Си, возможно, вы уже видели эти строки у нас на сайте или в любом другом учебнике. На самом деле, си-строки — это всего лишь массивы символов но, со своей спецификой, таким образом, мы всегда знаем, где конец строки. В этой статье мы рассмотрим несколько функций для работы со строками, например, вы — копирование, конкатенация, получить длину строки.

Что такое строки?

Отметим, что наряду со строками в стиле С, которые, по сути, являются простыми массивами, есть также строковые литералы, такие как этот «literal». В действительности, что строки, что литералы — это просто наборы символов, расположенных рядом в памяти компьютера. Но между массивами и литералами все таки есть разница, литералы нельзя изменять и строки — можно.

Любая функция, которая принимает строку в стиле С, также может принимать в качестве параметра — литерал. В си также есть некоторые сущности, которые могут выглядеть как строки, хотя, на самом деле, они таковыми не являются. Я сейчас говорю о символах, они заключены в одинарные кавычки, вот пример — ‘а’, как видите, это не строка. Символ можно, в определенном месте, присвоить строке, но символы не могут быть обработаны в виде строки. Если вы помните, массивы работают как указатели, поэтому, если вы передаете один символ в строку, это будет считаться ошибкой.

Из всего выше сказанного вы должны были понять, что строки — это массивы символов, а строковые литералы — слова, окруженные двойными кавычками. Вот еще один пример литерала:

«Это статическая строка»

Вы еще не забыли про специфику строк, которая упоминалась немного выше? Так вот, Си-строки всегда должны завершаться нулевым символом, буквально — ‘’. Поэтому, чтобы объявить строку, состоящую из 49 букв, необходимо зарезервировать дополнительную ячейку под нулевой символ:

char myString[50];

Как видно из примера, длинна массива — 50 символов, 49 из которых займет строка и один, последний займет нулевой символ. Важно помнить, что в конце си-строк всегда должен быть нуль-символ, точно так же как и в конце каждого предложения есть точка. Хотя нуль символ не отображается при выводе строки, он все-равно занимает место в памяти. Поэтому, технически, в массиве из пятидесяти элементов вы смогли бы сохранить только 49 букв, потому что, последний символ нужен для завершения строки. Кроме того, указатели также могут быть использованы в качестве строки. Если вы читали статью про выделение памяти в Си, вы можете сделать нечто подобное:

char *myString; // указатель типа char
myString = malloc( sizeof(*myString) * 64 ); // выделение памяти

В этом примере мы выделили 64 ячейки в памяти для массива myString. Для высвобождения памяти воспользуйтесь функцией free().

free( myString );

Использование строк

Строки полезно использовать тогда, когда вам необходимо выполнять различные операции с текстовой информацией. Например, если вы хотите, чтобы пользователь вводил имя в программу, вы должны использовать строку. Использование функции scanf() для ввода строки — работает, но это может привести к переполнению буфера. Ведь входная строка может оказаться больше, чем размер строки-буфера. Есть несколько способов для решения этой проблемы, но самый простой способ — это использовать fgets() функцию, которая объявлена в заголовочном файле <stdio.h>.

Когда fgets() считывает входные данные от пользователя, она будет читать все символы, кроме последнего. После этого в конец считанной строки, fgets() поместит нулевой терминатор. Функция fgets() будет cчитывать символы до тех пор, пока пользователь не нажмет Enter. Давайте посмотрим пример использования fgets():

#include <stdio.h>

int main()
{
char myString[100]; // длинная строка

printf( «Введите длинную строку: » );

fgets( myString, 100, stdin ); // считываем из потока ввода строку

printf( «Вы ввели следующую строку: %s», myString );

getchar();
}

Первым параметром для fgets() является строка, второй параметр — размер строки и третий параметр — это указатель на входной поток данных.

Результат работы программы:

CppStudio.com

Введите длинную строку: Судьба оставляет свой отпечаток
Вы ввели следующую строку: Судьба оставляет свой отпечаток

Для закрытия данного окна нажмите <ВВОД>…

Как видите, из вывода программы, во входную строку попал символ новой строки — ‘n’. Так случилось из-за того, что fgets() считала в строку myString нажатие кнопки Enter и завершила работу. Это означает, что вам может понадобиться вручную удалить символ новой строки. Один из способов сделать это, посимвольный перебор. Давайте доработаем программу и удалим символ новой строки:

#include <stdio.h>

int main()
{
char myString[100]; // длинная строка

printf( «Введите длинную строку: » );

fgets( myString, 100, stdin ); // читываем из потока ввода строку

int i;
for ( i = 0; i < 100; i++ )
{
if ( myString[i] == ‘n’ )
{
myString[i] = ‘’;
break;
}
}

printf( «Вы ввели следующую строку: %s», myString );

getchar();
}

Обратите внимание, что если входная строка содержит меньше 100 символов, то в строку попадет и символ новой строки. Поэтому мы можем удалить этот символ, используя простой перебор. В программу мы добавили цикл, в котором перебираем символы  строки, строки 12-19. И когда нам встречается символ новой строки, мы его заменяем нулевым символом, строка 16. Результат работы программы:

CppStudio.com

Введите длинную строку: Судьба оставляет свой отпечаток
Вы ввели следующую строку: Судьба оставляет свой отпечаток
Для закрытия данного окна нажмите <ВВОД>…

На этом пока все. В следующей статье я расскажу вам о специальных функциях для работы со строками.

P.S.: Все мы любим смотреть разные видео-записи, но иногда бывает так, что не всегда получается воспроизвести некоторые форматы видео-файлов. Так вот, решить эту проблему можно с помощью программы — xilisoft converter ultimate. Вы без труда сможете быстро переконвертировать видео из одного формата в другой. Кроме того, эта программа умеет конвертировать еще и аудио-файлы, и анимированные изображения.

Источник