- 스트링 관련 함수 v1.2-
1. 종류
int strcasecmp(const char *s1, const char *s2);
char *strcat(char *dest, const char *src);
char *strchr(const char *s, int c);
int strcmp(const char *s1, const char *s2);
int strcoll(const char *s1, const char *s2);
char *strcpy(char *dest, const char *src);
size_t strcspn(const char *s, const char *reject);
char *strdup(const char *s);
char *strfry(char *string);
size_t strlen(const char *s);
char *strncat(char *dest, const char *src, size_t n);
int strncmp(const char *s1, const char *s2, size_t n);
char *strncpy(char *dest, const char *src, size_t n);
int strncasecmp(const char *s1, const char *s2, size_t n);
char *strpbrk(const char *s, const char *accept);
char *strrchr(const char *s, int c);
char *strsep(char **stringp, const char *delim);
size_t strspn(const char *s, const char *accept);
char *strstr(const char *haystack, const char *needle);
char *strtok(char *s, const char *delim);
size_t strxfrm(char *dest, const char *src, size_t n);
char *index(const char *s, int c);
char *rindex(const char *s, int c);
2. 설명
C에서의 string은 단순히 0바이트로 끝나는 문자들의 배열이다. string에 관한 모든 타입과 선언은 string.h에 되어있다. string 처리를 위해 필요한 str1의 첫번째 주소를 알기위해선 &str1[0]대신 str1이라고만 하면 된다. C에서 배열의 변수명은 배열의 첫번째 주소를 가르킨다는 것은 이글을 보는 사람이라면 모두 알고 있을 것이다. 배열역시 내부적으로는 포인터로써 처리된다. 즉 str1[i]가 있다면 이의 실제 처리는 다음과 같이 된다. *(str1 + i) 함수의 인자로써 배열을 주는 경우가 종종있는데 이는 좋은 코딩이 아니다. 왜냐하면 인자로 넘겨주는 배열은 위와같이 다시 포인터로 변환되는 과정을 거쳐야 하기때문에 코드의 효율성을 떨어뜨린다.
int func(char []) -> int func(char *)
위와 같이 한다하여 배열과 포인터가 결코 같다고 생각해선 안된다. char i[10]; 하면 char 메모리를 10만큼 할당하는 것이지만 char *i; 하면 단지 메모리에 있는 문자를 가르키는 번지만이 할당된다. 다시 위의 함수의 인자를 넘겨주는 것에 대해 살펴보면 이와같은 차이에도 불구하고 왜 저렇게 해야하는지 의문이 들겠지만 함수에서는 인자로 값을넘겨줄때 배열의 모든값을 넘겨줄수가 없다. 따라서 그 번지를 넘겨줘야 되는데 어차피 같은 거라면 배열을 써서 다시 포인터로 변환되는 과정을 거칠필요가 없다는 것이다. 여기에서 더 깊이 들어가는것은 이 문서의 범위를 벗어나므로 더욱 자세한 사항은 포인터와 배열에 대한 책을 참고하기 바란다.(http://pw2.netcom.com/~tjensen/ptr/pointers.htm)
string 를 처리할때 가장많이 사용하는 함수로 strlen이 있는데 이는 아주 유용하다. strlen은 문자열의 길이를 리턴해준다(null 문자는 길이에서 제외된다). 다시 말하면 null 문자의 offset을 리턴하는 것이다. 보통 문제가 되는 것은 sizeof연산자 와의 혼동이다.
char a[1024] = "하하하 호호호 히히히 메롱"; 있다고 하면 sizeof(a) 는 1024이고 strlen(a)는 25이다.
앞으로 자주보게 될 size_t는 unsigned int형이다. 스트링 처리를 함에 있어 그 정확한 길이를 알고 싶은 경우가 많은데 거의 대부분의 해답은 strlen()가 된다. 그것으로 해결이 안되는 경우는 함수의 리턴값을 통해서 알 수 있는 경우도 있다. 예를들면 read()나 write()의 경우에는 리턴값이 읽거나 쓴 길이이다.
2.1 복사 함수
주의할 점은 두개의 영역이 겹쳐있으면 안된다. 만약 그런경우가 필요하다면
위해선 memmove를 사용하라.
리턴값은 dest를 가르키는 포인터
두 메모리 영역은 겹쳐져도(overlap) 된다.
리턴값은 dest를 가르키는 포인터
리턴값은 dest에서 c문자 다음위치를 가르키는 포인터. 실패는 NULL
리턴값은 s를 가르키는 포인터이다.
복사한다. 주의할 점은 dest는 복사되는 문자에 대해 모두 저장할 수 있어야한다.
리턴값은 dest를 가르키는 포인터이다.
만약 src가 가르키는 곳의 문자열 길이가 n보다 작다면 \0로써 패딩되어 복사된다.
strncpy를 사용하는 것이 strcpy에서
발생할 수있는 버그를 예방하는 것이긴 하지만 보통 이것은 수행속도가 느리다.
왜냐하면 상대적으로 작은 src를 상대적으로 큰 버퍼에 복사하는 과정에서 버퍼의
나머지영역을 널문자로 채우게 되는데 여기에서 상당한 시간을 보내기 때문이다.
넣는다(널문자까지). overlap은 허용되지 않으며 dest는 부여지게 될 string까지
커버할 수있는 메모리를 가지고있어야한다.
리턴값은 dest를 가르키는 포인터이다.
strcat는 다음과 같다.
char *
strcat (char *to, const char *from)
{
strcpy (to + strlen (to), from);
return to;
}
그런데 끝에 널문자까지 복사되기때문에 실제 복사되는 크기는 n + 1이 됨을
주의해야 한다.
src가 n보다 작다면 널문자로 패딩된다.
strncat의 리턴값은 a[0]에 대한 번지값이고 overlap은 허용되지 않는다.
리턴한다. 새로운 문자열을 malloc에 의해서 메모리를 할당 받고
free를 통해서 해제할 수있다. 만약 할당할 메모리가 없다면 널 포인터를
리턴한다.
char *name;
name = strdup("LHC");
free (name);
size가 문자열 s보다 작다면 size만큼 복사하고 끝에 널포인터를 넣어준다.
2.2 비교 함수
0을 작으면 음수값을 리턴한다.
임의의 배열에서 두개가 같은지 않같은지를 테스트하는데 이를 사용하는것은
아주 유용하다. 하지만 바이트 수가 다른 배열상의 byte-wise ordering 비교를
하는것은 의미없는 짓이다. 예를들면 실수형 포인터로 구성된 메모리상의
1 byte-wise ordering은 실수형 포인터 변수들사이의 관계에 대해 어떤것도
말해주지 않는다. 또다른 주의해야 할 사항으로 holes를 포함하는 객체들을
비교할 때이다. holes는 패딩하여 생기는 메모리 영역을 말하는데 패딩이란
예를들어 1byte씩 주고받는 상황이 있다고 할때 어떤 함수에서 5bit만을 리턴한다면
그를 규정에 맞게 주고 받기 위해선 8bit로 만들어야 한다. 이때 모자라는 3비트를
널로 채우든지 뭐로 채우든간에 이렇게 채우는 행위를 일컷는 용어이다.
이렇게 패딩된 holes의 내용은 무엇이 들어 갈지 알 수없기때문에 byte-wise 비교
수행시 문제를 일으킬수 있다. 따라서 결과에 대해 확실히 하려면 component-wise
비교를 해야한다.
struct foo
{
unsigned char tag;
union
{
double f;
long i;
char *p;
} value;
};
즉 위와 같은 foo의 멤버 데이터를 비교하기 위해선 memcpy를 사용하는것보다
다른 특별한 함수를 사용하는것이 더 낫다.
strcmp는 다음과 같이 이루어져있다.
int
strcmp(s1, s2)
register const char *s1, *s2;
{
while (*s1 == *s2++)
if (*s1++ == 0)
return (0);
return (*(const unsigned char *)s1 - *(const unsigned char *)(s2 - 1));
}
다음은 아스키 코드를 기반으로 하는 시스템에서의 결과이다.
strcmp ("hello", "hello")
=> 0 /* 같으므로 리턴값은 0 */
strcmp ("hello", "Hello")
=> 32 /* h와 H의 아스키 코드 차는 32 */
strcmp ("hello", "world")
=> -15 /* h와 w의 아스키 코드 차는 15 */
strcmp ("hello", "hello, world")
=> -44 /* 널문자와 ',' 와의 아스키코드 차는 44 */
strncmp ("hello", "hello, world", 5)
=> 0 /* 처음 5바이트는 같다 따라서 리턴값은 0*/
strncmp ("hello, world", "hello, stupid world!!!", 5)
=> 0 /* 처음 5바이트는 같다 따라서 리턴값은 0 */
2.3비교 정열을 위한 함수 ( collation function )
문자를 비교나 정열하기 위해선 사용하는 문자의 코드값을 알아야 한다. 예를 들면
알파벳의 경우 a는 0x61번의 아스키 코드를 갖는다. 이를 통하여 우리는
문자의 순서를 알수있다. 그런데 문제는 비영어권에서이다. 그들의 문자에 대한
코드값이 필요한다 일반적인 프로그램에서는 그에 대한 정보를 알수 없기때문에
정열을 할수없게된다. 그래서 필요한 것이 로케일이란 것인데 이것은 사용자가
사용하는 나라의 문자에 관한 정보를 가지고있다. 그래서 프로그램은 그 로케일을
참고하여 정열이나 기타 그 나라 문자에 관한 처리를 할수있는 것이다.
이렇게 현재의 로케일에 맞게 비교 해주는 함수로 strcoll과 strxfrm이 있다.
이들 함수들은 로케일 category중 LC_COLLATE값을 사용한다.
표준 C에서 strcoll의 비교 방식은 strcmp와 동일하다.
(예제)
qsort를 이용한 strcoll 사용예제.
int compare_elements (char **p1, char **p2)
{
return strcoll (*p1, *p2);
}
/* This is the entry point---the function to sort
strings using the locale's collating sequence. */
void sort_strings (char **array, int nstrings)
{
/* Sort temp_array by comparing the strings. */
qsort (array, sizeof (char *),
nstrings, compare_elements);
}
스트링을 strcmp 처리를 하면 변환되기전의 두개의 스트링을 strcoll 처리한것과
같은 결과를 갖게 된다. 변환된 n개 만큼의 문자들을 dest에 쓴다. 변환은
LC_COLLATE에 설정되 있는 현재 로케일을 따른다. overlap은 허용되지 않는다.
리턴값은 전체 변환된 문자의 바이트 수이고(\0는 제외) n에 영향 받지 않는다.
만약 변환된 문자열의 길이가 n과 같거나 크다면 dest에 들어간 내용은 정확하지
않음을 주의해야 한다. 정확한 dest를 얻으려면 다시한번 더큰 사이즈로 strxfrm을
호출해야한다. 만약 n이 0이면 어떤 문자도 dest에 복사하지 않고 단지 전체
변환된 문자열의 길이만을 리턴하고 dest의 사이즈에도 전혀 영향 받지안기 때문에
변환된 문자의 길이를 알때 유용하게 사용할 수있을것이다. 다음과 같이 하면 된다.
1 + strxfrm(NULL, "변환할 문자열", 0)
LC_COLLATE는 정규식에서 검색이나 스트링 정렬을 위해 사용되는 로케일 값이다.
로케일 category에 대해서 더 자세한 사항을 보려면 man setlocale로 확인하기
바란다. setlocale은 프로그램에서 사용할 로케일을 설정하는 함수이다.
현재 자신의 운영체제에서 사용중인 로케일을 확인하려면 locale란 명령을
주면된다.
[root@LHC rc.d]# locale
LANG=ko_KR.eucKR
LC_CTYPE="ko_KR.eucKR"
LC_NUMERIC="ko_KR.eucKR"
LC_TIME="ko_KR.eucKR"
LC_COLLATE="ko_KR.eucKR"
LC_MONETARY="ko_KR.eucKR"
LC_MESSAGES="ko_KR.eucKR"
LC_ALL=ko_KR.eucKR
[예제]
다음은 strcoll의 예제와 같은 역할을 하는 소스인데 스트링 변환을 1번만 하기
때문에 효율적이고 훨씬 빠르다. 여러번 비교를 할때 유용할 것이다.
struct sorter { char *input; char *transformed; };
/* This is the comparison function used with qsort
to sort an array of struct sorter. */
int
compare_elements (struct sorter *p1, struct sorter *p2)
{
return strcmp (p1->transformed, p2->transformed);
}
/* This is the entry point---the function to sort
strings using the locale's collating sequence. */
void
sort_strings_fast (char **array, int nstrings)
{
struct sorter temp_array[nstrings];
int i;
/* Set up temp_array. Each element contains
one input string and its transformed string. */
for (i = 0; i < nstrings; i++)
{
size_t length = strlen (array[i]) * 2;
char *transformed;
size_t transformed_lenght;
temp_array[i].input = array[i];
/* First try a buffer perhaps big enough. */
transformed = (char *) xmalloc (length);
/* Transform array[i]. */
transformed_length = strxfrm (transformed, array[i], length);
/* If the buffer was not large enough, resize it
and try again. */
if (transformed_length >= length)
{
/* Allocate the needed space. +1 for terminating
NUL character. */
transformed = (char *) xrealloc (transformed, transformed_length + 1);
/* The return value is not interesting because we know
how long the transformed string is. */
(void) strxfrm (transformed, array[i], transformed_length + 1);
}
temp_array[i].transformed = transformed;
}
/* Sort temp_array by comparing transformed strings. */
qsort (temp_array, sizeof (struct sorter), nstrings, compare_elements);
/* Put the elements back in the permanent array
in their sorted order. */
for (i = 0; i < nstrings; i++)
array[i] = temp_array[i].input;
/* Free the strings we allocated. */
for (i = 0; i < nstrings; i++)
free (temp_array[i].transformed);
}
2.4 검색함수
char를 찾는데 int c로 선언되 있음에 의아할 지도 모르겠지만 c는 찾는 과정에서
unsigned char로 변환된다. 리턴값은 찾았다면 문자가 위치한 곳의 포인터를
못찾으면 널포인터를 리턴한다.
부분으로써 간주된다. 따라서 C를 널문자로 줌으로써 문자열의 끝 번지를
알아내는데 이용할 수있다. 리턴값은 찾았다면 문자가 위치한 곳의 포인터를
못찾으면 널포인터를 리턴한다.
[예제]
strchr ("hello, world", 'l')
=> "llo, world"
strchr ("hello, world", '?')
=> NULL
리턴값은 찾으면 마지막 발생한 c의 주소를 못찾으면 널을 리턴한다.
널문자 역시 스트링의 일부로 간주된다.
[예제]
strrchr ("hello, world", 'l')
=> "ld"
문자열의 길이를 계산한다. 리턴값은 그 길이이다.
strspn은 string span이라 읽는다.
예를 들면
strspn ("hello, world", "abcdefghijklmnopqrstuvwxyz")
=> 5
accept로 구성된 문자가 안나올때까지의 길이는 5이다. ','는 accept에
정의되어 있지 않기 때문이다. 만약 accpet로 ", abcdefghijklmnopqrstuvwxyz"
해주면 13이라고 문자열 전체 길이를 출력할 것이다. accept 문자의 순서는 어떻게
되든 상관이 없다.
reject로 구성된 문자가 나올때까지의 길이를 리턴해 준다.
strcspn ("hello, world", ",");
=> 5
char *strpbrk(const char *s, const char *accept);
string point break라 읽으며 하는 읽은 strcspn과 비슷하다. 다른점은 accept에
정의된 문자가 처음 나오는 곳의 포인터를 넘겨준다는 점이다.
[예제]
strpbrk ("hello, world", " \t\n,.;!?")
=> ", world"
널문자는 비교하지 않는다.
리턴값은 찾으면 처음 발생한 곳의 포인터를 못찾으면 널을 리턴한다.
[예제]
strstr ("hello, world", "l")
=> "llo, world"
strstr ("hello, world", "wo")
=> "world"
처음 발생한 번지를 리턴한다.
이함수는 GNU 확장이다. libc 5.0.9에서 이 함수를 사용하는 것은 위험하다. 거기에는
needle와 haystack 인자가 서로 바뀌어 있고 needle이 처음 발생한 곳의 끝 번지를
리턴한다. libc 5.0.9는 여전히 많이 사용되고 있기때문에 이 함수의 사용은 위험
하므로 사용을 자제해야 한다.
또한 libc나 glibc둘다 다음과 같은 버그가 있다.
만약 needle이 널이라면 리턴값은 haystack가 아니라 haystack - 1이 된다.
glibc 2.0.5는 더욱 나쁜 상황을 연출하는데 이는 haystack의 마지막 바이트의
포인터를 리턴한다. 따라서 결코 needle를 널로 주고 사용해서는 안되며
정 필요한 경우가 아니면 사용을 하지 말아야 할 함수이다.
2.5 토큰 함수
이들은 간단한 구분 분석이나 파싱을 할때 사용된다.
char a[] = "하하하 이것은 토큰을 위한 예입지요";
char *p1, *p2, *p3, *p4, *p5;
p1 = strtok(a, " ");
p2 = strtok(NULL, " ");
p3 = strtok(NULL, " ");
p4 = strtok(NULL, " ");
p5 = strtok(NULL, " ");
delim이 처음 발생할 때까지의 문자를 찾고 그것을 널문자로 대체하고 문자열의
포인터를 리턴한다. 따라서 p1은 '하'를 가르키는 포인터가 된다. 그리고 다음
단어의 시작을 가르키는 포인터를 내부적으로 기억한다. 그렇기 때문에 첫번째
인자를 더이상 줄필요가 없으므로 NULL을 쓰는것이다.
다음 예제를 보면 확실히 이해가 될것이다.
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를 사용하면 string 함수의 내용이 바뀌게된다.
따라서 const형 string에는 사용할 수 없다. 또한 내용이 유지되야 하는 스트링에
대해 사용하려면 먼저 복사를 해논다음에 사용해야 한다.
string의 내용을 한문자씩 출력 해보면 다음과 같다.
wordsseparatedbyspaces-- and punctuation
delim에 주어진 문자들은 널포인터로 바뀌기 때문에 띄어쓰기가 안된것처럼 보인다.
"-- " 부분은 앞의 공간이 널로 변환되고 delim에 주어지지 않은 문자의 시작인
a를 가르키기 때문에 그와 같이 출력되고 and다음의 공간은 and 바로 다음의 ','가
널문자로 치환되고 delim에 주어지지 안은 첫단어의 시작인 punc~로 가기때문에
그와 같이 출력되는 것이다. 쓰레드 비안정적이다.
기억하지 않는다는 것이다. 따라서 호출할때 save_ptr을 수동으로 제공해 줘야 한다.
2번째 호출부터 newstring을 널로 하고 save_ptr을 그대로 주면 strtok와 같이
작동하게 된다. 또한 쓰레드 안정적이다.
이 함수는 POSIX.1b에서 제안되었다.
#include <stddef.h>
...
char string[] = "words separated by spaces -- and, punctuation!";
const char delimiters[] = " .,;:!-";
char *running;
char *token;
...
running = string;
token = strsep (&running, delimiters); /* token => "words" */
token = strsep (&running, delimiters); /* token => "separated" */
token = strsep (&running, delimiters); /* token => "by" */
token = strsep (&running, delimiters); /* token => "spaces" */
token = strsep (&running, delimiters); /* token => "and" */
token = strsep (&running, delimiters); /* token => "punctuation" */
token = strsep (&running, delimiters); /* token => NULL */
첫번째 인자를 부가적으로 줌으로써 중복적인 접근을 피할 수있다.
움직이는 포인터의 초기화는 사용자가 할수있다. strsep가 성공하면
delim에 의해 구분되는 토큰을 따라 포인터는 움직이게 된다.
리턴값은 다음 토큰의 처음을 가르키는 포인터이다. delim이 발견되지 않으면
null을 리턴한다.
4.3BSD에서 나온 함수이다.
2.6 기타 함수
다음은 1 부터 19까지 errnum에 대한 설명이다.
errornum 0 : 성공
errornum 1 : 명령이 허용되지 않음
errornum 2 : 그런 파일이나 디렉토리가 없음
errornum 3 : 그런 프로세스가 없음
errornum 4 : 중단된 시스템 호출
errornum 5 : 입력/출력 오류
errornum 6 : 장치가 설정되지 않았음
errornum 7 : 인수 명단이 너무 깁니다
errornum 8 : Exec 형식 오류
errornum 9 : 잘못된 파일 기술자
errornum 10 : 자식 프로세스가 없음
errornum 11 : 자원이 일시적으로 사용 불가능함
errornum 12 : 메모리를 할당할 수 없습니다
errornum 13 : 허가 거부됨
errornum 14 : 잘못된 주소
errornum 15 : 블럭 장치가 필요함
errornum 16 : 장치나 자원이 동작 중
errornum 17 : 파일이 존재합니다
errornum 18 : 부적절한 장치간 연결
errornum 19 : 그런 장치가 없음
리턴한다.
[참고자료]
Linux man page
Programming Language - C
love_C ( Cambridge University Engineering Department)
The GNU C Library Reference Manual