Стандартная C библиотека для Linux, часть 7:
Работа со строками |
Наконец нашлось время (через несколько лет), чтобы передать Linux-сообществу следующую серию статей, посвященных стандартной библиотеке C. Надеюсь вам понравится. Предыдущая моя статья была посвящена библиотеке <assert.h>. В этой статье мы рассмотрим <string.h> -- работу со строками. Для работы со строками язык C приспособлен немногим лучше, чем низкоуровневые языки программирования, так что программисты, работающие с такими языками, будут чувствовать себя как дома. string.h имеет множество ограничений и узких мест, которые мы обсудим в при описании соответствующих функций. Я допускаю, что читатели знакомы с языком C, поэтому сразу предупреждаю, что не даю никакой гарантии ни по поводу точности приводимой здесь информации, ни в пригодности для каких либо целей. Пример rogers_example07.c был написан для демонстрации работы каждой из строковых функций. Скомпилировав и запустив его, можно наблюдать работу кода. Как всегда, если вы встретите ошибки -- пожалуйста, сообщите мне, в будущем я сделаю исправления. В конце статьи можно посмотреть исправления, касающиеся предыдущих статей. ВНИМАНИЕ: Копирование строк -- один из самых опасных разделов программирования в C. C не предусматривает проверку на выход за границы массивов, поэтому очень легко "выскочить" за пределы строки (это называется "переполнение буфера"), "затирая" другие переменные и даже вызвать крах программы. Крякеры используют это свойство языка и неопытность кодировщиков, преднамеренно вызывая переполнение буфера, заставляя программу "вывалиться" в командную строку, тем самым получая доступ к учетной записи, под которой выполнялась программа, а ведь на серверах множество программ исполняются с правами root! В действительности в C строк нет. Я понимаю, что довольно странно прочесть этом в статье, посвященной работе со строками в C, но, тем не менее, это правда. То, что мы называем строками -- просто массивы символов. Чтобы выделить под строку память, необходимо указать компилятору ее размер, в большинстве случаев это выглядит, как создание простого массив символов: char string[17]; Здесь выделяется место для 16-ти символов и одного символа -- признака конца строки. strcpy ( string, "This is a string" ); Этот вызов копирует строку "This is a string" в ранее зарезервированное место. Копируемая строка содержит 16 символов и завершается специальным символом -- ASCII nil. В данном случае в массиве string достаточно места для ее размещения. Nil обычно представляется числом ноль, или символом '\0', или '\000'. Самое удивительное заключается в том, что иногда этот код будет работать, даже когда будет копироваться строка, содержащая более 17 символов: strcpy ( string, "This is a long string" ); Поскольку никакой проверки на выход за границы выделенной области памяти не производится, то в результате "затирается" память, принадлежащая другим переменным. Из-за этого программа может неожиданно "упасть", причем в месте, весьма далеком от того, где было выполнено копирование строк с выходом за границы массива. Взломщики могут использовать такое переполнение буфера для получения доступа к командной оболочке атакуемой системы. Это один из серьезных аргументов в пользу того, чтобы отказаться от использования функции strcpy. Вместо нее можно использовать strncpy: #define MAX_STRING_LENGTH 17 char string[MAX_STRING_LENGTH]; strncpy ( string, "This is a long string", MAX_STRING_LENGTH ); string[MAX_STRING_LENGTH-1] = '\000'; Причина, по которой я определил макрос MAX_STRING_LENGTH для указания длины строки, в том, что это значение может использоваться во многих местах программы и, если вдруг вздумается изменить длину строки, то не придется искать число 17 по всему тексту программы, чтобы его изменять: достаточно будет изменить это число в одном месте. Иногда числовое значение может использоваться в разных контекстах (т.е. обозначать не только размер буфера, но и что нибудь еще). Однако, если литерал используется как число в одном и том же контексте, но в разных местах, то определение макроса делает его легко заметным в тексте программы и позволяет вносить изменения достаточно простым образом, в данном случае -- легко изменить размер буфера строки. Обратите внимание на последнюю строку string[MAX_STRING_LENGTH-1] = '\000';, для чего она? Дело в том, что при копировании строки, содержащей бОльшее количество символов, чем передано функции strncpy, то завершающий ноль не будет записан в строку-результат. Такая "незавершенная" строка может привести к краху программы и очень трудно обнаруживаемой ошибке. Существует возможность динамического выделения памяти для строк с помощью функций malloc, realloc и calloc. Эти функции выделяют память по запросу во время исполнения программы. Это более сложный, но вместе с тем и более гибкий и мощный способ. #define STATIC_STRING "This is a long string that will be copied into\ a location during runtime" char *string; int string_length; string_length = strlen(STATIC_STRING); if ( !(string = (char *) malloc ( string_length )) ) { /* no memory left, die */ exit (1); } strncpy( string, STATIC_STRING, string_length); string[string_length] = '\000'; /* операции над строкой */ free(string); Одно из узких мест данного способа в том, что память
необходимо освобождать функцией free. Если вы забудете
где-нибудь освободить память, то произойдет
"утечка" памяти и в конечном счете ваша программа
исчерпает доступную память системы и "рухнет". <string.h> имеет массу проблем. Самая большая проблема в том, что эта библиотека не является окончательно завершенной и полностью устойчивой. В действительности <string.h> является коллекцией функций, написанных различными разработчиками в разное время и объединенных в библиотеку. Большая часть функций возвращает NULL или указатель на строку. Если функция может возвращать NULL, то необходимо проверять возвращаемое значение и предусматривать действия для NULL-результата. Если попытаться трактовать значение NULL, как указатель на строку, то это скорее всего приведет к краху программы. Я сгруппировал функции по их назначению, указывая при этом небольшие различия между похожими функциями. Можно было выделить в отдельные группы функции, предназначенные для работы со строками и для работы с памятью, но, на мой взгляд, это непрактично. Копирование
#include <string.h>
void *dest: указатель на массив-приемник, сюда
копируются данные.void *memcpy(void *dest, const void *src, size_t
n); char *dest: указатель на строку-приемник, сюда копируются данные. const void *src: указатель на массив-источник, данные копируются отсюда. const char *src: указатель на строку-источник, данные копируются отсюда. size_t n: количество копируемых символов. Эти функции возвращают указатель на dest, что довольно странно, поскольку этот указатель и так известен. memcpy копирует n символов из области памяти, адресуемой указателем src, в область памяти, адресуемую указателем dest. Эта функция не предусматривает копирование перекрывающихся областей памяти. memmove также копирует n символов из области памяти, адресуемой указателем src, в область памяти, адресуемую указателем dest. Но сначала данные копируются во временную область памяти, а затем в память, адресуемую указателем dest, так что эта функция может использоваться для копирования данных в перекрывающиеся области памяти. strncpy Копирует не более чем n символов из области памяти, адресуемой указателем src, в область памяти, адресуемую указателем dest. Функция завершает свою работу по тому из двух условий, которое выполнится первым: либо встретится завершающий null-символ, либо будет скопировано заданное количество символов. Если скопировано n символов, а null символ не встретился, то в строку-приемник завершающий null символ не записывается, т.е. строка остается "открыто", поэтому желательно всегда записывать ноль в конец строки приемника после выполнения копирования. strcpy Копирует строку, адресуемую
указателем src, в строку, адресуемую указателем dest, включая
завершающий null-символ. Я уже показывал, как используются функции strcpy и strncpy, функции memcpy и memmove используются аналогичным образом, с той лишь разницей, что последние две могут копировать любые блоки данных, а не только строки. Слияние (конкатенация) строк
#include <string.h>
char *dest : указатель на строку-приемник
(результат).char *strcat(char *dest, const char *src); const char *src: указатель на строку-источник (присоединяемая строка). size_t n: количество копируемых символов. strcat Содержимое строки src, включая завершающий символ '\0', копируется в конец строки dest. Копирование начинается в позицию символа '\0' строки-приемника. strncat Аналогично strcat, но копируется не более чем n символов. Функция добавляет символ '\0' в конец строки-приемника после копирования. И strcat , и strncat возвращают указатель на строку dest. Опять же, функции не производят проверку на выход за пределы строки-приемника, так что всегда проверяйте достаточно ли места зарезервировано для строки-приемника. Сравнение
#include <string.h>
const char *s1: указатель на первую строку.int memcmp(const void *s1, const void *s2, size_t
n); strxfrm(char *s1, const char *s2, size_t n);
прим. перев.)
const void *s1: указатель на первую область памяти. const char *s2: указатель на вторую строку. const void *s2: указатель на вторую область памяти. size_t n: количество сравниваемых символов. memcmp лексикографически сравнивает n байт. Если s1 меньше чем s2, то возвращается число меньшее нуля. Если s1 равно s2, то возвращается ноль. Если s1 больше чем s2, то возвращается число большее нуля. strcmp сравнивает две строки s1 и s2. Строки должны завершаться нулевым символом. Если s1 меньше чем s2, то возвращается число меньшее нуля. Если s1 равно s2, то возвращается ноль. Если s1 больше чем s2, то возвращается число большее нуля. Сравнение основано на числовых значениях ASCII символов. strncmp очень похожа на memcmp, за исключением того, что сравниваются две строки. Функция производит сравнение не более чем n символов. Если строки (или одна из строк) короче n, то сравнение заканчивается по достижению завершающего нулевого символа Если s1 меньше чем s2, то возвращается число меньшее нуля. Если s1 равно s2, то возвращается ноль. Если s1 больше чем s2, то возвращается число большее нуля. strcoll сравнивает две строки s1 и s2. Если s1 меньше чем s2, то возвращается число меньшее нуля. Если s1 равно s2, то возвращается ноль. Если s1 больше чем s2, то возвращается число большее нуля. Сравнение производится с учетом настройки локали (национального алфавита и других языковых особенностей), устанавливаемой вызовом функции setlocale() из библиотеки <locale.h>. Более подробно об этой функции мы поговорим в одной из следующих статей. strxfrmФункция strxfrm() преобразует строку s2 в такую форму, что выполнение strcmp() над двумя такими строками, преобразованными посредством strxfrm(), будет таким же, как и выполнение strcoll над исходными строками. Первые n символов преобразованной строки помещаются в s1. Преобразование основывается на настройках категории текущей локали LC_COLLATE. Функция strxfrm() возвращает количество байтов, скопированных в s1, без учета завершающего символа '\0'. Если возвращенное значение равно n или больше этой величины, то содержимое s1 не определено и должно трактоваться как ошибка. Поиск
#include <string.h>
const void *s: указатель на массив, в котором
производится поиск.void *memchr(const void *s, int c, size_t
n); int c: искомый символ. size_t n: количество просматриваемых символов. memchr ищет первое вхождение символа c в массиве s, просматривая при этом не более n символов. Возвращает указатель на символ c или NULL, если таковой не найден. strcspn возвращает длину начального сегмента строки s, состоящего только из символов, не указанных в строке reject. strspn возвращает длину начального сегмента строки s, состоящего только из символов строки accept. strpbrk возвращает указатель на первое вхождение в строке s любого символа из строки accept. Если таких символов не обнаружено, то возвращается NULL. strchr возвращает указатель на местонахождение первого совпадения с символом c в строке s или NULL, если такой символ не найден. strrchr возвращает указатель на местонахождение последнего совпадения с символом c в строке s (т.е. поиск производится с конца строки в направлении к ее началу прим. перев.). Если символ не найден, то возвращается NULL. strstr возвращает указатель на первую встретившуюся подстроку substring в строке s или NULL, если таковая не встретилась. Man page strtok не рекомендует использовать эту функцию из-за некоторых связанных с ней проблем. Функция strtok делит исходную строку на элементарные подстроки-токены. На первом вызове функции передается указатель на строку и функция возвращает указатель на первый токен. На каждом последующем вызове, функции, в качестве первого аргумента передается NULL, а функция будет возвращать токен за токеном до тех пор, пока не вернет NULL в качестве результата Разделители могут отличаться при каждом последующем вызове. Функция имеет множество ограничений: она изменяет оригинальную строку s, строка разделителей не сохраняется от вызова к вызову и функция не может работать со строками-константами. (т.е. вызов типа strtok("This is a string of tokens", " "); работать не будет прим. перев.). Разное
#include <string.h>
void *svoid *memset(void *s, int c, size_t n); int c size_t n int errnum const char *s memset заполняет массив размером n значением c и возвращает указатель на начало массива. strerror возвращает указатель на строку с описанием кода ошибки, переданного в аргументе errnum или сообщение о неопознанной ошибке, если код ошибки неизвестен. Работает с различными кодами ошибок, присутствующими в библиотеках <stdio.h> и <error.h>. В одной из следующих статей мы коснемся этой темы глубже. strlen возвращает количество символов в строке s. Завершающий символ '\0'не учитывается. Непереносимые функции Библиотека для работы со строками GNU имеет ряд функций, не поддерживаемых стандартом C. Описание их взято из man pages. Если необходимо, чтобы программа работала на любой UNIX системе, использования этих функций следует избегать. Они, однако, могут служить прекрасным руководством для создания функций в ваших собственных переносимых программах. int strcasecmp(const char *s1, const char *s2); strcasecmp сравнивает строки s1 и s2 без учета регистра символов. Если s1s2, то возвращается число меньшее нуля. Если s1 равно s2, то возвращается ноль. Если s1 больше чем s2, то возвращается число большее нуля. int strncasecmp(const char *s1, const char *s2, size_t n); strncasecmp похожа на предыдущую функцию, но сравнивает не более чем n символов. strcasecmp и strncasecmp возвращают целое число. Если s1 меньше чем s2, то возвращается число меньшее нуля. Если s1 равно s2, то возвращается ноль. Если s1 больше чем s2, то возвращается число большее нуля. char *strdup(const char *s); Я реализовал эту функцию у себя, поскольку ничего не знал о ней! Я постоянно нахожу для себя что-то новенькое в Linux. strdup возвращает указатель на новую строку, которая является точной копией s. Память под вновь созданную строку выделяется с помощью функции malloc(3), и может быть освобождена вызовом free(3). strdup возвращает указатель на новую строку или NULL в случае нехватки памяти. char *strfry(char *string); strfry изменяет порядок расположения элементов (символов) в строке при помощи rand(3). В результате получается анаграмма строки. strfry возвращает указатель на переупорядоченную строку. char *strsep(char **stringp, const char *delim); strsep возвращается указатель на следующий токен из строки stringp который ограничен символом из строки delim. Токен завершается символом '\0', а stringp обновляется так, чтобы указывать на место сразу после извлеченного элемента. Похожа на функцию strtok(), но является непереносимой. strsep возвращает указатель на элемент строки (токен), если символ из delim не найден, то возвращает исходное значение *stringp. char *index(const char *s, int c); index возвращает указатель на первый встретившийся в строке s символ c. Для поиска символа в строке лучше использовать функцию strchr(), которая является переносимой. char *rindex(const char *s, int c); rindex возвращает указатель на последний символ c в строке s. Завершающий символ '\0' рассматривается как часть строки. Для поиска символа в строке лучше использовать функцию strrchr(), которая является переносимой. index и rindex возвращают указатель на найденный символ или NULL, если символ не найден. Исправления ошибок, встретившихся в предыдущих статьях:Все правильно! Я наконец передаю в публикацию накопленные исправления ошибок, допущенных в предыдущих статьях. Вы только посмотрите, какие ошибки я допустил! Спасибо тем, кто нашел время отправить мне сообщение. Subject: The Standard C Library for Linux,
Part Three" Hej James M. Rogers Вы написали в The Standard C Library for Linux, Part Three "putchar записывает символ в стандартный поток
вывода. putchar(x) это то же самое, что и Вероятно Вы имели ввиду "...fputc(x, STDOUT) Lars Hesdorf Ответ: Я уверен, что употребление заглавных символов (STDOUT) неверно. Я полагаю, что должно быть "fputc(x, stdout)". В примере программы все указано правильно, поскольку я ее тестировал на корректность. Subject: The Standard C Library for
Linux, Part Two Уважаемый, в The Standard C Library for Linux, Part Two Вы написали " char *fgets(char *s, int n, FILE *stream); char *s строка, в которую заносится результат. fgets считывает до n символов из потока в строку. char s[1024]; но fgets() в действительности читает до n-1 символов, так что не следует специально резервировать место для символа \0 (если n установить равным размеру строки). Tim McCormack Ответ: Subject: snprintf in Article C Library for
Linux? Я не знаком с функцией snprintf, но на мой взгляд
использование этой функции Но я впервые увидел ее в библиотеке C только для LINUX Полагаю Вы могли бы в Ваших статьях отмечать функции,
характерные только для Все равно snprintf -- "Хорошая штука" TM. Спасибо за Ваши статьи. Очень хорошо написаны и очень Ответ: Subject: Standard C Programming Library Part
3 Так как Вы просили исправления.... ------------Вы написали: Все параметры, передаваемые функции "scanf"
должны быть указателями т.е. предваряться символом
(&): Надеюсь, что это поможет Ответ: Subject: character handling
program Привет! в Ваших программах, в Linux gazette отсутствует вызов
setlocale() С уважением, Ответ: Библиография:The ANSI C Programming Language, Second Edition, Brian W. Kernighan, Dennis M. Ritchie, Printice Hall Software Series, 1988The Standard C Library, P. J. Plauger, Printice Hall P T R, 1992 The Standard C Library, Parts 1, 2, and 3, Chuck Allison, C/C++ Users Journal, January, February, March 1995 STRING(3), BSD MANPAGE, Linux Programmer's
Manual Предыдущие статьи "The Standard C Library for Linux"The Standard C Library for Linux, stdio.h, August 1998 The Standard C Library for Linux, stdio.h, September 1998 The Standard C Library for Linux, ctype.h, March 1999 The Standard C Library for Linux, stdlib.h, April 1999 The Standard C Library for Linux, assert.h, May 1999 James RogersДжеймс Роджерс -- системный программист, специализирующийся в области маршрутизаторов Cloverleaf для HL7. Сейчас он также работает над открытой библиотекой для работы с HL7. (вообще, HL7 -- это вроде бы всегда был один из стандартов на обмен данными в организациях здравоохранения. Интересно... прим. ред.). C помощью этой библиотеки он надеется создать открытый "движок" для работы с HL7. Copyright © 2002, James M Rogers.
|
Вернуться на главную страницу |