5 문자열과 배열 유틸리티

문자열(또는 문자의 배열) 처리는 많은 프로그램에 있어 중요한 부분이다.GNU C 라이브러리는 많은 문자열 유틸리티 함수를 제공하는데, 여기에는 문자열의 복사, 연결, 비교, 검색을 위한 함수들이 포함되어 있다. 많은 문자열 처리함수들은 저장소의 임의의 구역을 처리할 수 있다; 예컨대, memcpy 함수는 어떠한 종류의 배열 내용도 복사해 낼 수 있다.

초보 C 프로그래머들이 자신의 코드로 이 기능을 중복되게 작성하여 "도구를 재발명"하는 일은 너무도 흔한 일이다. 그러나 그렇게 함으로써 라이브러리 함수들에 친숙해지고 그것들을 사용할 수 있게되므로, 이러한 일은 유지, 효율성, 운용에 이점을 제공하는 셈이다.

예컨대, 당신은 2행의 C 코드에서 하나의 문자열을 다른 문자열과 쉽게 비교할 수 있다. 만약 당신이 기존의 strcmp함수를 사용한다면 실수를 줄일 수 있다. 또한 이러한 라이브러리 함수들은 전형적으로 매우 최적화 되어 있으므로 당신의 프로그램은 빠르게 실행될 수도 있다.


5.1 문자열의 표현

이 절에서는 초보 C 프로그래머들을 위해 문자열의 개념을 간명하게 요약하였다.C에서 문자열이 어떻게 표현되며 흔히 있을 수 있는 함정은 무엇인가를 기술해 놓았다. 만약 당신이 이미 이러한 내용들을 잘 알고 있다면, 이 절을 건너뛰어도 좋다.

문자열은 char 대상물의 배열이다. 그러나 문자열 값을 가지는 변수들은 항상 문자포인터(char *) 형태로 선언된다. 그러한 포인터 변수들은 어떤 문자열의 내용물을 위한 공간을 포함하고 있지 않다; 그 내용물은 어떤 다른 곳에 저장된다_즉, 어떤 배열 변수, 문자열 상수, 또는 동적으로 할당된 메모리 (3장.[메모리 할당] 참조)에 저장된다. 선택된 메모리 공간의 주소를 포인터 변수에 저장하는 일 당신의 몫이다. 선택적으로 당신은 포인터 변수에 널(null) 포인터를 담을 수도 있다. 이 널 포인터는 어떠한 곳도 가리키지 않는 것이므로 그것이 가리키는 문자열을 참조하려고 시도한다면 에러를 만날 것이다.

관행적으로 널 문자 '\0'은 문자열의 끝을 가리킨다. 예를 들어, char형 포인터 변수 p가 문자열의 끝을 가리키는 널 문자를 가리키는지 아닌지를 알아보고 싶으면, !*p 또는 *p == '\0'이라고 써보아라. 널 문자는 개념상으로 널 포인터와는 아주 다르다. 물론 그것들이 모두 정수 0으로 표현되기는 하지만.

C 프로그램 소스에서 문자열 리터럴은 이중 인용부호('"')사이에 있는 문자들의 문자열로서 나타난다.ANSI C에서는 문자열 리터럴은 문자열의 연결에 의해서 만들어질 수도 있다."a" "b"는 "ab"와 같다. 문자열 리터럴의 변경은 GNU C 컴파일러에서는 허용되지 않는다. 왜냐하면, 리터럴은 읽기 전용의 저장소에 위치하기 때문이다. <역자주석>리터럴:C 명령문 상에서 직접 입력하는 데이터 const로 선언된 문자 배열들 역시 변경이 금지된다. 변경금지의 문자열 포인터를 const char * 형태로 선언하는 것은 일반적으로 훌륭한 방식이다. 이렇게 하면, C 컴파일러는 당신의 프로그램이 그 문자열을 갖고서 무엇을 하고자 하는지에 관한 정보를 상당히 얻을 수 있으며, 또한 그 문자열의 우연한 변경에 대해 조사할 수 있게 된다.

문자 배열에 할당된 메모리의 양은 문자열의 끝 부분임을 통상적으로 표시하는 널 문자를 넘어서는 곳까지 확장될 수도 있다. 이 책에서는 할당크기(allocation size)라는 용어는 항상 문자열에 할당된 메모리의 총량을 가리키게 될 것이고, 반면에 길이(length)라는 용어는 종료시키는 널 문자까지의(포함하지는 않음) 문자의 개수를 가리키게 된다.

악명 높은 프로그램 소스의 버그는 문자열에서 그것의 적합한 크기보다 더 많은 문자를 집어넣으려고 하는 것이다. 문자열을 확장하고 이미 할당된 배열에 문자를 이동시키는 소스를 쓸 때, 당신은 항상 신중하게 텍스트의 길이를 추적해야 하며 배열이 넘치지 않는지를 분명하게 체크하여야한다. 많은 라이브러리 함수들이 알아서 이것을 해주지는 않는다! 그리고 당신은 문자열의 끝을 나타내는 널 문자를 담아둘 여분의 한 바이트를 할당해야함을 잊지 말아야 한다.


5.2 문자열과 배열 규정

이 장에서는 임의의 배열이나 메모리 블럭에서 작동되는 함수들과 널로 종료되는 문자의 배열에 한정되어 사용되는 함수들에 대해 설명한다. 임의의 배열이나 메모리 블럭을 처리하는 함수들은 'mem'(memcpy처럼)으로 시작되는 명칭을 가지며 항상 처리할 메모리 블럭의 크기를(바이트로) 지정하는 인수를 갖는다. 이 함수들의 배열 인수와 그 반환 값은 void * 형태를 가지며, 형식상 이러한 배열의 요소들은 "바이트"로서 참조된다. 당신은 이 함수들에게 어떤 종류의 포인터를 전달할 수 있으며, size 인수의 값을 계산하는데는 sizeof 연산자가 유용하다.

대조적으로, 특별히 문자열을 처리하는 함수들은 'str'로 시작하는(strcpy와 같이) 명칭을 가지며, 분명한 크기의 인수를 전달받기를 요구하는 대신에 문자열을 종료시키는 널 문자를 요구한다.(이러한 함수들 중에 어떤 것들은 특정한 최대 길이를 받아들이지만, 역시 널 문자를 써서 사전 종료를 체크하고 있다.) 이러한 함수들의 배열 인수와 반환 값은 char * 형태를 가지며, 배열 요소들은 "문자들"로서 참조된다.

많은 경우에 한 함수에는 'mem' 버전과 'str' 버전이 모두 갖춰져 있다. 그 중의 한편의 함수는 훨씬 더 적합하게 사용할 수 있으나 정확한 상황을 요구한다. 만약 당신의 프로그램이 임의의 배열이나 저장소의 블럭을 처리하려 한다면, 당신은 'mem'함수들을 쓰는 편이 좋다. 반면에, 당신이 사전에 문자열의 길이를 알지 못한 상태에서 널로 끝나는 문자열을 처리할 때는 str'함수들을 사용하는 편이 편리하다.


5.3 문자열 길이

당신은 strlen 함수를 사용하여 문자열의 길이를 구할 수 있다.

이 함수는 'string.h'에 선언되어 있다.

함수 size_t strlen (const char *s)

함수 strlen은 널로 종료되는 문자열 s의 길이를 반환한다.(다른 말로 표현하면, 그것은 배열내의 종료하는 널문자의 오프셋을 반환한다.)
예를 들면,
strlen ("hello, world")
한 문자의 배열에 적용해보면, strlen 함수는 그곳에 저장된 문자열의 길이를 반환하는 것이지, 그것의 할당 크기를 반환하는 게 아니다. 문자의 할당 크기는 sizeof 연산자를 사용하여 구할 수 있다.
char string[32] = "hello, world";
/*위의 배열에는 31개의 문자와 종료 널 문자 1개를 담을 수 있다*/
sizeof (string)
) 32 /*할당된 배열의 크기는 32*/
strlen (string)
) 12 /*실제의 문자열의 길이는 12*/


5.4 (문자열) 복사와 결합

이 절에서 서술해 놓은 함수들은 문자열과 배열을 복사하거나 한 문자열을 다른 문자열에 결합할 때 사용할 수 있다. 이 함수들은 'string.h'에 선언되어 있다.

이 함수들의 인수 기입 순서를 외우는 방법은 소스 배열의 왼편에 목적 배열을 두어서 할당 표현과 일치시키는 것이다. 이러한 함수들 전부가 목적 배열의 주소를 반환한다.

이 함수들의 대부분은 소스 배열과 목적 배열이 중첩될 경우에 제대로 작동하지 않는다. 예를 들면, 목적 배열의 시작 부분이 소스 배열의 끝 부분과 중첩되게 되면, 소스 배열 중의 그 부분의 내용이 복사되기 전에 덮이게 된다. 더욱 심각한 것은, 문자열 함수의 경우에, 문자열의 끝을 나타내는 널문자가 상실되고, 그 복사 함수는 당신의 프로그램에 할당된 모든 메모리를 돌아다니는 루프에 빠져버릴 것이다.

중첩된 배열사이에서 복사하는 문제를 갖는 모든 함수들이 이 안내서에서 분명하게 지적되어있다. 이 절의 함수들뿐만 아니라 sprintf(7.9.7 [형식화된 출력 함수] 참조.)나 scanf(7.11.8 [형식화된 입력 함수] 참조.)와 같은 몇몇 함수들이 있다.

함수 void * memcpy (void *to, const void *from, size_t size)

memcpy 함수는 from에서 시작되는 대상물로부터 to로 시작되는 대상물로 size 바이트를 복사한다. 만약 2개의 배열 from과 to가 중첩될 경우의 행위는 정의되어있지 않다; 중첩될 경우에는 이 함수 대신에 memmove를 사용하라. memcpy가 반환하는 값은 to의 값이다. 어떤 배열의 내용물을 복사하기 위해 memcpy 함수를 사용하는 예를 들어보자:
struct foo *oldarray, *newarray;
/*foo라는 명칭의 struct 가 2개의 포인터에 할당되고*/
int arraysize;
. . .
memcpy (new, old, arraysize * sizeof(struct foo));
/* old의 내용을 new에 <struct 크기 * 특정숫자>크기만큼 복사하라는 것*/

함수 void * memmove(void *to, const void *from, size_t size)

memmove는 2개의 블럭 공간이 중첩될 경우에조차도 from에서 size 바이트를 to의 size 바이트에 복사한다. 중첩이 있을 때, memmove는 주의 깊게 from블럭에 있는 바이트에서 최초의 값을 복사하는데, 거기에는 to블럭에 속하는 바이트도 포함되어 있기 때문이다.

함수 void * memccpy(void *to, const void *from, int c, size_t size) Function

이 함수는 from에서 to로 정확히 size 바이트만큼만 복사하고,c에 일치하는 한 바이트가 발견되면 중지한다. 반환 값은 c가 복사된 곳 바로 다음의 바이트에 대한 포인터이며, 만일 from의 최초의 size 바이트에 c에 일치하는 바이트가 없으면 널 포인터를 반환한다.

함수 void * memset (void *block, int c, size_t size)

이 함수는 c의 값을(unsigned char로 변환하여) block 에서 시작되는 대상물의 최초의 size 바이트의 각 부분에 복사한다. 이 함수는 블럭의 값을 반환한다.

함수 char * strcpy (char *to, const char *from)

이 함수는 문자열 from(종료하는 널 문자까지 포함하여)으로부터 문자열 to로 문자들을 복사한다.memcpy처럼 문자열이 중첩되게 되면 이 함수도 알 수 없는 결과를 초래한다. 반환 값은 to의 값이다.

함수 char * strncpy (char *to, const char *from, size_t size)

이 함수는 strcpy와 비슷하지만 항상 정확히 size 바이트만큼만 to에 복사한다. 만약 from의 길이가 size보다 크면, 이 함수는 앞부분의 size 바이트를 복사한다. 만약 from의 길이가 size보다 작으면, 이 함수는 from의 모든 것을 복사하고 그 크기만큼의 공백을 널 문자로 채운다. 이와 같은 행위는 별로 유용하지 않지만 ANSI C 표준에 의해 규정된 사항이다.
 
문자열이 중첩될 경우의 strncpy의 행위는 정의되어 있지 않다.strcpy와는 다르게 strncpy를 사용하는 것이 to의 할당된 공간의 끝 부분 이후에 널 문자를 채움으로써 버그를 피하는 수단이 된다. 그러나, 어떤 경우에는 프로그램의 실행을 느리게 하기도 한다: 큰 버퍼에 작은 문자열을 복사하는 경우이다. 이런 경우에는, size가 너무 커서 strncpy가 널 문자를 채우는 시간이 상당히 낭비되는 것이다.

함수 char * strdup (const char *s)

이 함수는 널로 종료되는 문자열 s를 새롭게 할당되는 문자열로 복사한다. 문자열은 malloc을 써서 할당된다. 3.3 [제한 없는 할당] 참조.
만약 malloc이 새로운 문자열을 위한 공간을 할당할 수 없게되면,strdup는 널 포인터를 반환한다. 그렇지 않으면 이 함수는 새로운 문자열에 대한 포인터를 반환한다.

함수 char * stpcpy (char *to, const char *from)

이 함수는 strcpy와 같으나 한가지가 다르다. 이 함수는 시작부분의 포인터가 아니라 문자열 to의 끝 부분에 대한 포인터(즉, 종료하는 널문자의 주소)를 반환한다.
예를 들면, 다음 프로그램은 stpcpy 함수를 사용해서 'foobar'를 만들기 위하여 'foo'와 'bar'를 결합하였고, 그런 다음에 그것을 출력해 보았다.
#include <string.h>
#include <stdio.h>
 
int main (void)
{
char buffer[10];
char *to = buffer; /*버퍼의 값을 to에 넣고?*/
to = stpcpy (to, "foo"); /*문자열 "foo"를 to에 복사*/
to = stpcpy (to, "bar"); /*문자열 "bar"를 "foo"가 담긴 to에 복사*/
puts (buffer); /*버퍼의 문자열을 꺼내보면, "foobar"가 되었겠지?*/
return 0;
}
 
이 함수는 ANSI나 POSIX 표준의 일부분이 아니며, 유닉스 체제에서 일상적인 것도 아니지만, 우리가 이것을 만들어 낸 것도 아니다. 아마도 이것은 MS-DOS(?)에서 만들어진 것 같다. 문자열이 중첩될 경우의 행위도 정의되어 있지 않다.

함수 char * strcat (char *to, const char *from)

strcat 함수는 strcpy와 유사하지만 덮어쓰는 대신에 from의 문자들을 to의 끝에 결합하거나 추가하는 점만 다르다. 즉,from의 처음 문자가 to의 끝 부분을 나타내는 널 문자를 덮어쓰게 된다.
 
strcat와 같은 정의는 아마 이렇게 될 것이다:
char * strcat (char *to, const char *from)
{
strcpy (to + strlen (to), from);
/*strcpy(to,from);은 from이 to의 기존내용을 덮어쓰지만, 위의 것은 to의 기존주소에다 to의 문자열 길이 만큼을 더한 주소에 from을 복사하므로,to의 널 문자를 제외한 기존 문자열이 그대로 살아남는다는 뜻?*/
return to; /*주소가 반환되남?*/
}
 
이 함수에도 문자열이 중첩될 경우가 정의되어 있지 않다.

함수 char * strncat (char *to, const char *from, size_t size)

이 함수는 strcat와 같으나,from의 size 문자만큼만 to의 끝 부분에 추가된다는 점에서 다르다. 이 함수 역시 하나의 널 문자를 to의 끝에 추가하며, 그리하여 to의 할당된 전체 크기는 그것의 최초의 길이보다 적어도 size+1 이상이 되어야 한다.
strncpy 함수는 아래와 같이 만들어졌을 것이다.
 
char *strncat(char *to, const char *from, size_t size)
{
strncpy (to + strlen (to), from, size);
return to;
}
 
이 함수 역시 문자열 중첩의 경우를 정의하지 않고 있다. 다음에 strncpy와 strncat의 예를 보자. strncat 함수를 호출할 때 문자 배열 버퍼가 중첩되는 것을 피하기 위해서 size 파라미터가 계산되는 방식에 주의하라.
 
#include <string.h>
#include <stdio.h>
#define SIZE 10
 
static char buffer[SIZE]; /*음..정적 변수는 그 값이 불변으로 저장?*/
main ()
{
strncpy (buffer, "hello", SIZE); /*버퍼에 "hello"를 10 바이트만 복사*/
/*그러면 buffer가 h e l l o \0 \0 \0 \0 \0 이렇게 되는 것?*/
puts (buffer);
strncat (buffer, ", world", SIZE - strlen(buffer) - 1);
/*음..10-5-1,즉,4만큼만 ", world"를 추가하면 결국, ", wo"만 결합되는군*/
puts (buffer);
}
 
이 프로그램의 출력은 아래와 같다.
hello
hello, wo

함수 void * bcopy (void *from, const void *to, size_t size)

이 함수는 BSD에서 유래된 것으로서 memmove와 대체할 수 있어 쓸모 없게 된 함수다. 다만 인수를 배치하는 순서가 다른 점이 있어서 memmove와 완전히 같을 수 없음에 유의하라.

함수 void * bzero (void *block, size_t size)

이 함수는 BSD에서 유래된 것으로서 memset와 대체할 수 있어 쓸모 없게 된 함수이며, 이 함수가 저장할 수 있는 유일한 값은 영(0)이다. 어떤 장치들은 제로 화된 메모리를 위한 특별한 도구들을 갖추고 있는 경우가 있는데, 이때는 bzero가 memset보다 훨씬 효과적이다.


5.5 문자열/배열 비교

당신은 문자열과 배열의 내용을 비교하기 위해서 이 절에 있는 함수들을 사용할 수 있다. 비교뿐만 아니라 정렬을 할 때 순서관계를 결정하는 함수로서도 사용할 수 있다. 15장 [Searching and Sorting] 참조.

다른 대부분의 비교와 달리 C에서, 문자열 비교 함수는 그 비교의 결과가 동등하지 않을 경우 0이 아닌 값을 반환한다. 그 값의 의미는 비교한 문자열에서 동등하지 않은 첫 번째 문자의 순서관계를 가리킨다. 즉 음수 값은 첫 번째 문자열이 두 번째 문자열보다 작다는 걸 의미하고 그 값이 양수이면 첫 번째 문자열이 두 번째 문자열보다 크다는 걸 의미한다.

만약 당신이 단순하게 동등한지 아닌지 만을 체크하기 위해 이 함수들을 사용한다면 당신은 밑에 있는 것처럼 매크로 정의를 이용하여 좀더 깨끗한 프로그램을 만들 수도 있다.

#define str_eq(s1,s2) (!strcmp ((s1),(s2)))

이 함수들은 헤더파일 'string.h'에 정의되어 있다.

함수: int memcmp (const void *al, const void *a2, size_t size)

이 함수는 메모리의 a1로 시작하는 size만큼을 a2로 시작하는 같은 size만큼과 비교하는 함수이다. 바이트( unsigned char objects로 해석되고, int로 승격된)의 첫 번째 다른 쌍이 나타나면 다름을 나타내는 신호를 반환한다. 만약 두 블록의 내용이 동등하면 memcmp는 0을 반환한다. 정해지지 않은 배열에서 memcmp함수는 동등함을 비교하는데 대개는 유용하게 쓰인다. 그렇지만 어떤 배열에서 한 바이트별로 크기 비교를 할 때는 유용하지 않다. 예를 들어, 플로팅 포인트가 있는 여러 바이트에서 한 바이트 비교는 실수들값 사이의 관계에 대해서 아무 것도 말해주지 않는 것이다.
 
당신이 "holes(구멍)".. 즉.. unions의 끝에 덧붙여진 공백, 문자열의 끝에 덧붙여진 문자들처럼 그들이 실제로 저장된 공간보다 전체 길이를 더 차지하게 되고, 강제로 필요에(alignment) 의해 요구된 어떤 object들에 덧붙여진 것과 같은 "holes"를 포함할 수 있는 objects를 비교하기 위해 memcmp를 사용하는 것은 주의가 필요하다. 이들 "구멍들"은 바이트별 비교를 수행할 때 애매하고 이상한 작동의 원인이 될 수 있고 더 나아가서는 충돌 성분별 비교를 수행하는 것과 같은 결과를 낸다.
 
예를 들어 밑의 구조체형 정의처럼;
struct foo{
unsigned char tag;
union
{
double f;
long i;
char *p;
} value;
};
 
당신은 구조체 foo 와 같은 object들에 비교함수를 쓸 때는 memcmp대신에 특별 화된 비교함수를 사용하는 것이 더 좋다.

함수: int strcmp (const char *s1, const char *s2)

strcmp함수는 문자열 s1과 s2를 비교해서 첫 번째 다른 문자의 ( unsigned char로 object를 해석하고 int로 승격된 ) 쌍이 나오면 그 다음에 해당하는 값을 반환한다. 만약 두 개의 문자열이 같으면strcmp는 0을 반환한다. strcmp를 사용했을 때 만약 s1이 s2와 앞쪽의 문자열이 같은 substring(부문자열)이라면 s1이 s2보다 작다고 간주한다.

함수: int strcasecmp (const char *s1, const char *s2)

이 함수는 무시되는 경우의 차이를 제외하면 strcmp와 같다.

함수: int strncasecmp (const char *s1, const char *s2)

이 함수는 무시되는 경우의 차이를 제외하고는 strncmp와 같다. strncasecamp는 GNU확장이다.

함수: int strncmp (const char *s1, cnost char *s2, size_t size)

이 함수는 비교될 문자들의 길이가 인수로 필요한걸 제외하면 strcmp와 비슷하다. 다른 말로 하면 만일 두 개의 문자열이 주어진 크기 안에서 그 안에 있는 문자들이 같다면 반환 값은 zero(0)이다. 여기에 strcmp와 strncmp의 사용을 보여주는 몇 가지 예가 있다. 이 예들은 ASCII 문자 셋을 사용한다고 가정하자. ( 만약 ASCII 대신에 EBCDIC과 같은 다른 문자 셋을 사용하면 glyphs(문자)를 다른 숫자 코드들로 연관시켜서 결과 값이나 순서관계가 우리의 예상과 다를 수 있다.
 
strcmp ("hello", "hello")
) 0 /* 이 두 개의 문자열은 같다. */
strcmp ("hello", "Hello")
) 32 /* 비교는 case-sensitive 하다.( 즉 대, 소문자 구별을 한다..) */
strcmp ("hello", "world")
) -15 /* 문자 'h'는 문자 'w'보다 앞에 존재한다. 즉 h가 w보다 적다는 얘기겠지요. */
strcmp ("hello", "hello, world")
) -44 /* 컴마(,)와 널문자의 비교. 공백이 작다. */
strncmp ("hello", "hello, world"", 5)
) 0 /* 앞에서 5개 까지는 문자가 같다. */
strncmp ("hello, world", "hello, stupid world!!!", 5)
) 0 /* 앞에서 5개 까지는 문자들이 같다. */

함수: int bcmp (const void *a1, const void *a2, size_t size)

이 함수는 BSD확장으로 memcmp의 퇴화된 기능을 가졌다고 보면 된다.


5.6 대조 함수들

어떤 지역들에서는, 사전 편찬상의 순서가 문자 코드들의 엄격한 숫자적 순서와는 다르다. 예를 들어 스페인에서 대부분의 문자(glyphs)는 비교할 목적일 때 하나의 독립적인 문자로 간주되지 않는 악센트와 같은 발음 구별 기호가 있다. 한편으로는 두 문자의 문자열인 'll'이 'l'의 바로 다음순서를 갖는 단일 문자열로 취급되는 경우도 있다.

당신은 strcoll 과 strxfrm 함수( 헤더파일 'string.h'에 정의된.)를 사용하여 현재 지역에서 사용하는 대조순서를 사용하여 문자열을 비교할 수 있다. 이 함수들이 사용될 지역은 LC_COLLATE 분류에 속한 지역을 선택하여 지정할 수 있다. 19장 [Locals] 참조.

표준 C 에서는 strcoll을 위한 비교 문자열도 strcmp와 같다.

효과적으로, 이 함수들을 사용하려면 문자열 안에 있는 문자들을 현재지역의 대조열 안의 문자 위치를 표현하는 byte열로 문자열 안의 문자를 변형시켜 대응되게 사용하면 된다. 간단한 형태의 byte열을 비교함은 지역의 대조열로 문자열을 비교하는 것과 같다. strcoll함수는 비교를 하기 위한 순서의 하나로, 암묵적으로 이 변형을 수행한다. strxfrm와 비교하면 strxfrm은 명시적으로 이 변형을 수행한다. 만일 당신이 동일한 문자열이나 문자열들의 셋을 다중비교하기 원한다면 일단 모든 문자열들은 변형하기 위해 strxfrm을 사용하고 이어서 strcmp로 변형된 문자열들을 비교하는 것이 더 효과적일 것이다.

함수: int strcoll (const char *s1, const char *s2)

strcoll함수는 strcmp와 비슷하지만 어떤 지역의 문자를 대조하는데 쓰인다. (the LC_COLLATE locale) 여기에 strcoll을 사용하여 비교하기 위해 문자열들을 정렬하는 예가 있다. 이 실제 정렬 알고리즘은 여기에 쓰여지지 않았다; qsort이다. ( 15.3절[Array Sort Function] 참조 ) 여기에 나타난 프로그램 코드들은 그들을 정렬하는 동안 문자열들을 비교하기 위해 어떻게 하는지를 말해주고 있다. ( 이 절의 끝에 strxfrm을 사용하여 이것을 더 효과적으로 수행하는 방법이 있다. )
 
/* 이것은 qsort가 사용된 비교함수이다. */
int compare_elements (char **p1, char **p2)
{
return strcoll (*p1, *p2);
}
 
/* 이것은 지역 대조열을 사용하여 문자들을 정렬하기 위한 함수의 진입지점이다. */
void sort_strings (char **array, int nstrings)
{
/* 문자열을 비교하여 임시문자열을 정렬하다. */
qsort (array, sizeof (char *),
nstrings, compare_elements);
}

함수: size_t strxfrm (char *to, const char *from, size_t size)

이 함수는 현재의 지역으로 선택된 대조 문자열을 사용하여 문자열을 변형시기는 일을 하고 이 변형된 문자열을 배열 안에 저장한다. character의 크기로 저장된다.
( 종료 널 문자가 포함된) 문자열에서 문자열로 대치되는 행동은 정의되지 않았다. 5.4절[Copying and Concatenation] 를 보라. 반환 값은 변형된 문자열의 전체길이이다.
크기가 값으로서 유용하지는 않지만 만일 그것이 크기보다 크다면 그것은 변형된 문자열이 배열크기에 전체적으로 맞지 않다는 것을 의미한다. 이 경우에 오직 그 문자열의 양만큼 실제적으로 맞추어서 저장된다.
 
전체적으로 변형된 문자열을 얻으려면 strcfrm 을 좀더 큰 출력 배열을 사용해서 다시 호출하라. 변형된 문자열은 원래의 문자열보다 길 수도 짧을 수도 있다. 만약 크기가 zero라면 어떤 문자들도 안에 저장되어지지 않았다. 이 경우 strxfrm은 변형된 문자열의 길이가 되어질 문자들의 수를 반환한다.
 
이것은 할당하기 위해 문자열의 크기를 결정하는데 유용하게 쓰인다. 만약 그 크기 가 zero라고 하는 것은 문제가 되지 않는다; 심지어 널 포인터라고 할지라도 여기의 예는 당신이 많은 비교를 수행하려고 계획할 때 strxfrm을 어떻게 사용할 수 있는지를 보여준다.
이것은 전의 예와 같은 것이지만 좀더 빠른데 왜냐하면 다른 문자열과 몇 번의 비교를 수행하던지 상관없이 단 한번의 변형을 위한 작업을 하기 때문이다. 심지어 저장공간을 할당하고 해제하기 위해 필요한 시간이 우리가 많은 문자열들을 저장하기 위한 필요한 시간보다 더 조금 필요하다.
 
struct sorter { char *input; char *transformed; };
/* 이것은 struct sorter의 배열에 정렬하기 위해 qsort를 사용한 비교함수 이다. */
 
int compare_elements (struct sorter *p1, struct sorter *p2)
{
return strcmp (p1->transformed, p2->transformed);
}
 
/* 이것은 지역 대조열을 사용하여 문자열을 정렬하기 위한 함수의 진입지점이다. */
void sort_strings_fast (char **array, int nstrings)
{
struct sorter temp_array[nstrings];
int i;
 
/* temp_array를 정한다. 각 요소는 하나의 입력 문자열과 그 변형된 문자열을 포함한다. */
for (i = 0; i < nstrings; I++) {
size_t length = strlen (array[i]) * 2;
temp_array[i].input = array[i];
 
/* array[i]를 변형하라. 첫째로 버퍼가 충분히 큰지를 시험해 보라. */
while (1) {
char *transformed = (char *) xmalloc (length);
 
if (strxfrm (transformed, array[i], length) < length) {
temp_array[i].transformed = transformed;
break;
}
free (transformed); /* 큰 버퍼로 다시 시도하라 */
length *= 2;
}
} /* for 의 끝 */
 
/* 비교할 변형된 문자열들에 의해 temp_array를 정렬한다. */
qsort (temp_array, sizeof (struct sorter), nstrings, compare_elements);
 
/* 그들을 정렬된 순서로 영구적인 배열 안에 요소들을 저장하라. */
for (i = 0; i < nstrings; i++)
array[i] = temp_array[i].input;
 
/* 우리가 할당했던 문자열을 해제하라 */
for (i = 0; i < nstrings; i++)
free (temp_array[i].transformed);
}

 

호환성 참조 : 이 문자열 대조 함수들은 ANSI C에 새로 첨가된 것으로 오래된 C에서는 이와 동등한 작업을 하는 것은 아무 것도 없다.


5.7 탐색 함수들

이 절은 문자열과 배열들에 적용할 다양한 종류의 탐색 오퍼레이션을 수행하는 라이브러리 함수들을 설명하고 있다. 이 함수들을 헤더파일 'string.h'에 정의되어 있다.

함수: void * memchr (const void *block, int c, size_t size)

이 함수는 블록에서 object의 시작부터 지정된 몇 바이트 크기 내에서 byte c( unsigned char로 변화시킨 )가 첫 번째 나타난 곳을 찾는다. 반환 값을 바이트가 위치한 곳의 포이터이거나 만약 발견하지 못하면 널 포인터이다.

함수: char * strchr (const char *string, int c)

이 strchr 함수는 문자c 가 첫 번째 나타난 곳을 스트링의 시작부터 널 종료문자까지, 그 안에서 찾는다. 반환 값은 문자가 위치한 곳을 가리키는 포인터이거나 발견하지 못하면 널 포인터이다.
 
예를 들어,
strchr ("hello, world", 'l')
) "llo, world"
strchr ("hello, world", '?')
) NULL
 
위의 strchr은 'l'이 hello안에 있으므로 그 위치를 포인터로 반환하게 된다. 그래서 그 포인터를 참조하면 "llo, world" 가 나오는 것이다. 밑의 것은 '?'가 없으므로 널 포인터를 반환.
종료 널 문자는 문자열의 일부분으로 간주된다. 그래서 당신은 c 인자 값으로 널 문자를 줄 수 있고, 그러므로 당신은 문자열의 끝을 가리키는 포인터를 이 함수를 사용해서 얻을 수도 있다.

함수: char * index (const char *string, int c)

index는 strchr의 다른 이름이다; 그들은 거의 동일하다.

함수: char *strrchr (const char *string, int c)

이 함수 strrchr은 문자열의 앞에서, 앞으로 진행하며 문자열을 검색하는 대신에 문자열의 뒤에서 반대방향으로 탐색하는 것을 제외하면 strchr과 같다,
예를 들어
strrchr ("hello, world", 'l')
) "ld"
뒤에서부터 찾아서 그 위치를 포인터로 넘기니까..그 포인터를 참조하면 "ld"값이 나오겠죠.

함수: char * rindex (const char *string, int c)

rindex는 strrchr의 다른 이름이다.; 그들은 거의 동일하다.

함수: char * strstr (const char *haystack, const char *needle)

이 함수는 단일 문자보다는 긴 substring을 탐색하는 것을 제외하면 strchr과 같다. 이것은 찾고자 하는 문자열(haystack)에서 찾기를 원하는 문자열이(needle)나타난 첫 번째 문자의 위치를 포인터로 반환하고 그렇지 않으면 널 포인터를 반환한다. 만약 찾고자 하는 문자열이 공백문자열 이라면 haystack을 반환한다.
 
예를 들어
strstr ("hello, world", "l")
) "llo, world"
strstr ("hello, world", "wo")
) "world"
 
밑에 strstr만 보자면 "wo"의 문자열을 "hello, world"에서 찾으니까 있기 때문에 'w'의 위치를 가리키는 포인터를 반환 합니다. 그래서 그 포인터를 참조하면 "world"가 되죠.

함수: void * memmem (const void *needle, size_t neede_len, const void *haystack, size_t haystack_lne)

이것은 strstr과 같지만 needle과 haystack은 널 종료를 가진 문자열이 아니라 byte array이다. needle_len은 needle의 길이이고 haystack_len은 haystack의 길이이다. 이 함수는 GNU확장이다.

함수: size_t strspn (const char *string, const char *skipset)

이 strspn("string span") 함수는 문자열 sring에서 skipset을 구성하는 문자들로만 이루어진 처음 substring을 찾아서 그 길이를 반환한다. skipset안에서 문자들의 순서는 중요하지 않다.
 
예를 들어
strspn ("hello, world", "abcdefghijklmnopqrstuvwxyz")
) 5
"hello, world"에서 뒤의 문자들로만 구성된 부문자열을 찾으면 hello까지가 되겠죠.. (,)는 뒤의 문자열에 포함되지 않았으니까... 그래서 길이가 5.

함수: size_t strcspn (const char *string, const char *stopset)

strcspn("string complement span") 함수는 stopset의 문자열을 구성하는 문자들로 구성되지 않은 string의 처음 substring의 길이를 반환한다. ( 달리 말하자면 stopset의 문자 셋의 멤버인, string안의 첫 번째 문자가 있는 offset을 반환한다. )
 
예를 들어
strcspn ("hello, world", " \t\n,.;!?")
) 5
 
hello다음에 뒤의 문자 셋에 포함된 ','가 나왔으니까, 처음부터 뒤의 문자 셋에 포함되지 않는 길이는 즉 hello의 길이가 되는 거죠. 그래서 반환 값은 5.

함수: char * strpbrk (const char *string, const char *stopset)

strpbrk ("string pointer break") 함수는 strcspn이 처음 substring의 길이를 반환 하는 대신에 strpbrk는 stopset의 문자 셋 멤버인 string의 첫 번째 문자의 포인터를 반환 하는 것을 제외하면 strcspn과 연관 되어있다. 만일 문자를 발견하지 못하면 널 포인터를 반환한다.
 
예를 들어
strpbrk ("hello, world", " \t\n,.;!?")
) ", world"
 
','가 나타난 곳의 위치를 포인터로 반환 하므로 그 포인터를 참조하면 ", world"가 되는 것이다.


5.8 문자열에서 토큰 찾기

프로그램이 보통의 string을 토큰으로 분리하는 것처럼 어구나 어휘분석과 같은 그런 간단한 종류의 일을 하는 것은 꽤 일반적이다. 당신은 헤더파일 'string.h'에 정의된 strtok함수로 이 일을 할 수가 있다.

함수: char * strtok (char *newstring, const char *delimiters)

string은 호출된 일련의 strtok함수를 통해 토큰들로 분리되어 질 수 있다. 분리시킬 문자열을 오직 첫 번째 호출에서 newstring 인수로 인식된다. strtok함수는 내부적 상황 정보를 맞추기 위해 이것을 사용한다. 같은 문자열에서 부가적으로 토큰을 얻기 위해 다음 호출을 할 때는 newstring인수로 널 포인터를 주어서 지정시킨다. 당신이 strtok를 호출한 뒤에 어떤 라이브러리 함수도 strtok를 호출하지 않았음을 보장한다.delimiters인수는 추출될 토큰을 둘러싸고 있는 구획문자의 셋을 지정한 문자열이다.

이 문자열 셋에서 첫 번째 문자들은 버려진다. 이 구획문자 셋의 멤버가 아닌 첫 번째 문자는 다음 토큰의 시작을 표시한다. 토큰의 끝은 구획문자 셋의 멤버인 다음 문자를 찾음으로서 발견된다. newstring인수로 쓰여진 원래의 문자열 안의 이 문자는 널 문자로 다시 쓰여지고 newstring안의 토큰의 시작점을 가리키는 포인터를 반환한다. ( 제가 이해한 바에 따르면 구획문자를 찾아서 토큰을 얻으면 그 구획문자는 널 문자로 대체시켜 버린다는 말인 것 같은데... 아마 맞을걸요...? )

strtok의 다음 호출에서 다음 문자의 시작점은 전의 토큰의 끝으로 표시된 점을 하나 지난 지점이다. 구획문자의 셋들을 일련의 strtok 호출에서 모든 호출이 모두 같은 것은 아님을 기억하라. (즉, 구획문자열들은 strtok를 호출할 때마다 달라도 된다는 얘기)

만일 newstring 문자열의 끝에 도달되었거나 문자열의 나머지가 오직 구획문자로만 구성되어 있다면 strtok는 널 포인터를 반환한다.

 

주의: strtok는 파싱하는 문자열을 변화시킨다, 그러므로 당신은 항상 strtok로 파싱하기 전에 임시 버퍼에 문자열을 복사해 놓아야 한다. 만약 당신이 당신 프로그램의 다른 부분에서 온 문자열을 수정하도록 strtok에 허용하면 당신이 스스로 문제를 자처하는 것이다; 그 문자열은 strtok에 의해 변형되어 파싱하는 동안 그 데이터가 다른 목적으로 사용되어 질 수도 있다.

심지어 상수일지도 모르는 문자열에 당신이 명령을 내리면 strtok는 그것을 수정하려 시도할 것이고 당신의 프로그램은 결국 ROM(read only memery)에 쓰기를 시도하므로 심각한 에러신호를 얻을 것이다. 21.2.1절 [Program Error Signals] 참조.

이것은 일반적인 원칙의 특별한 경우이다: 만일 프로그램의 일부분이 어떤 데이터 구조체를 수정하려는 목적을 가지고 있지 않다면 그것을 임시적으로 수정하려 할 때 에러가 발생하는 경향이 있다. strtok함수는 재진입하지 않는다. 21.4.6절 [Nonereentrancy] 재 진입하는 이유나 위치의 중요성에 대한 논의를 위해, 여기에 strtok의 간단한 사용 예가 있다.

#include <string.h>
#include <stddef.h>
. . .
 
char string[] = "words separated by spaces -- and, punctuation!";
const char delimiters[] = " .,;:!-";
char *token;
. . .
/* 여기서 구획문자는 공백(" ") 과 콜론, 세미콜론, 느낌표, 대시("-") 이네요... */
token = strtok (string, delimiters); /* token => "words" */
token = strtok (NULL, delimiters); /* token => "separated" */
token = strtok (NULL, delimiters); /* token => "by" */
token = strtok (NULL, delimiters); /* token => "spaces" */
token = strtok (NULL, delimiters); /* token => "and" */
token = strtok (NULL, delimiters); /* token => "punctuation" */
token = strtok (NULL, delimiters); /* token => NULL */
/* strtok는 구획문자가 나오면 그것을 토큰으로 잘라서 반환 시키네요.... */


목차 이전 : 4. 문자 다루기 다음 : 6. 입출력 개요