프린세스 다이어리

[C] C언어에서 문자열을 다루는 방법, 문자열 관련 함수 정리 본문

C, C++

[C] C언어에서 문자열을 다루는 방법, 문자열 관련 함수 정리

개발공주 2021. 9. 28. 12:00
728x90

1. 문자열의 개념

 

앞서 살펴보았듯 C에서는 문자열이 따로 없고 문자의 배열로 이를 표현한다. 특이점은 문자열은 컴퓨터 메모리 구조 상 마지막에 널(null) 값을 포함한다는 점이다. 어떠한 값도 의미 있지 않다는 의미다. 

0 1 2 3 4 5 6 7 8 9
H I   E U N J I N ₩0 (\0)

null값이 들어가는 이유는 문자열의 마지막임을 알리기 위해서다. 그래서 printf() 함수를 실행하면, 컴퓨터는 내부적으로 null값을 만날 때까지 출력하는 것이다. 

 

2. 문자열 리터럴과 포인터

 

문자열 형태로 포인터를 사용할 수 있다. 배열 형태가 아닌, 포인터 자체에 문자열을 마치 상수처럼 읽기 전용으로 넣는 것이다. 이 때, 큰따옴표 안에 문자열을 넣게 되는데 이걸 문자열 리터럴 이라고 하고, 컴파일러가 알아서 문자열 자체가 남아 있는 메모리에 담길 수 있도록 주소를 만들어진다. 그 주소를 포인터가 가지는 것이다. 읽기 전용이기 때문에 변경이 불가능하며 굳이 문자열을 바꾸고 싶다면, 다른 문자열을 해당 포인터가 가리키게 하는 방식을 취해야 한다. 

 

#include <stdio.h>

int main(void) {  
    char *a = "Hi Eunjin";   
    printf("%s\n", a);
}

C에서 문자열은 내부적으로 배열 형태로 처리가 되고 포인터로 치환될 수 있으니, 포인터 자체 값인 a를 출력하게 만들면 null값을 만나는 순간까지 출력하게 만든다. 

#include <stdio.h>

int main(void) {  
    char *a = "Hi Eunjin";   
    printf("%c\n", a[0]);
    printf("%c\n", a[3]);
    printf("%c\n", a[6]);
    printf("%c\n", a[8]);
}
H
E
j
n

배열로 처리되기 때문에 원하는 인덱스에 해당하는 문자를 따로 출력할 수도 있다. 

 

3. 문자열 입출력 함수

 

3-1. gets() 함수

 

이전까지 별생각 없이 사용자의 입력을 받기 위해 사용했던 scanf() 함수는 공백을 만날 때까지 입력을 받는 함수고, gets() 함수는 공백까지 포함해서 한 줄을 입력받는 함수다. 

 

#include <stdio.h>

int main(void) {  
    char a[100];
    gets(a);
    printf("%s\n", a);
}
warning: this program uses gets(), which is unsafe.
wtf this code is rubbish
wtf this code is rubbish

공백이 포함된 문자열을 입력해도 하나의 문자열이라고 인식되고 있다. 그런데 "warning: this program uses gets(), which is unsafe." 이라는 워닝이 떠서 알아보니 gets() 함수는 입력받을 문자의 개수를 설정하지 못하기 때문에 버퍼의 overflow를 일으킨다고 한다. gets() 함수는 C99 표준에서 deprecated 되었기 때문에 fgets()를 대신 쓰라는 글이 있어 바꾸어 보았다. 

 

3-2. fgets() 함수

 

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

int main(void) {  
    char a[100];
    fgets(a, sizeof(a), stdin);
    printf("%s\n", a);
}

 

fgets()의 인자 세 개 중, 첫 번째는 입력받은 문자열, 두 번째는 입력받은 문자열의 최대 크기('\0' 포함), 마지막은 file로 표준 입력장치(키보드)에서 데이터를 입력받겠다는 의미다. sizeof()는 C에서 제공하는 기본 함수로 배열의 전체 크기를 구하는 함수다.

hello eunjin
hello eunjin

➜  cstudy

한 줄이 더 출력되는 것은 fgets()로 입력을 받으면 '\n'가 알아서 문자열 끝에 들어가기 때문이다. printf()에서 개행문자를 빼면 된다. 

 

3-3. gets_s() 함수

 

또 다른 옵션은 gets_s() 함수를 사용하는 것이다. C11 표준부터 추가된 gets_s() 함수는 버퍼의 크기를 벗어나면 입력을 받지 않는 함수다. 두 번째 인수로 입력받은 문자열의 크기를 받는다. 

#include <stdio.h> 

int main(void) {  
    char a[100];
    gets_s(a, sizeof(a));
    printf("%s\n", a);
}
main.c:5:5: error: implicit declaration of function 'gets_s' is invalid in C99
      [-Werror,-Wimplicit-function-declaration]
    gets_s(a, sizeof(a));
    ^
main.c:5:5: note: did you mean 'gets'?
/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include/stdio.h:168:7: note: 
      'gets' declared here
char    *gets(char *);
         ^
1 error generated.

 

그런데 또 implicit declaration of function 'gets_s' 에러가 뜨면서 컴파일이 안 되는 것이었다. 이유를 찾아보니 gcc 등 비 윈도우 컴파일러는 gets_s() 함수를 사용하지 못할 수 있다고 한다. define을 추가하여 우회하는 방법도 써 봤는데 결과는 똑같았다. 

#include <stdio.h> 
#define gets(s) gets_s(s)

int main(void) {  
    char a[100];
    gets_s(a, sizeof(a));
    printf("%s\n", a);
}

 

어차피 gets_s()는 optional한 함수이니 일단은 fgets() 함수를 사용하고, 특정 코테 프로그램 환경에서 어떤 함수를 써야 컴파일이 될지는 그때 가서 다시 고민해보도록 해야겠다.

 

참고

http://andyader.blogspot.com/2014/03/gets.html

https://www.acmicpc.net/blog/view/52

 

4. 문자열 처리를 위한 함수 종류

 

C에서의 문자열 함수는 <string.h> 라이브러리에 포함돼 있다. 대표적인 함수는 다음과 같다.

strlen() 문자열의 길이 반환
strcmp() 문자열 1이 문자열 2보다 사전적으로 앞에 있으면 -1, 뒤에 있으면 1 반환
strcpy() 문자열 복사
strcat() 문자열 1에 문자열 2 더함
strstr() 문자열 1에 문자열 2가 어떻게 포함되어 있는지 반환

 

4-1. strlen()

 

#include <stdio.h> 

int main(void) {  
    char a[20] = "the real programmer";
    printf("문자열의 길이: %d\n", strlen(a));
}
main.c:5:41: error: implicitly declaring library function 'strlen' with type
      'unsigned long (const char *)' [-Werror,-Wimplicit-function-declaration]
    printf("문자열의 길이: %d\n", strlen(a));
                                  ^
main.c:5:41: note: include the header <string.h> or explicitly provide a declaration for
      'strlen'
main.c:5:41: warning: format specifies type 'int' but the argument has type 'unsigned long'
      [-Wformat]
    printf("문자열의 길이: %d\n", strlen(a));
                           ~~     ^~~~~~~~~
                           %lu
1 warning and 1 error generated.

또 에러가 났다. 비주얼 스튜디오에서는 그냥 strlen을 사용할 수 있다고 해서 vscode도 될 줄 알았다. 일단 strlen이 포함된 <string.h> 라이브러리는 추가해주어야겠고, 'unsigned long' 워닝을 해결하기 위해 printf() 함수를 수정해주었다. 

 

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

int main(void) {  
    char a[20] = "the real programmer";
    printf("문자열의 길이: %ld\n", strlen(a));
}
문자열의 길이: 19

잘 해결이 되었다. 

 

4-2. strcmp()

 

사전순 비교를 위해 코드를 짰다. 

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

int main(void) {  
    char a[20] = "the real programmer";
    char b[20] = "eunjin lee";
    printf("두 배열의 사전순 비교: %d\n", strcmp(a, b));
}
두 배열의 사전순 비교: 15

출력된 결과는 -1도, 1도, 0도 아닌 15였다. 뭔가 이상해서 글자 수도 맞춰보고 다양하게 문자열을 넣어 봤는데도 계속 15가 나왔다. 다시 찾아보니 문자열이 같지 않을 경우에 -1, 1이 아닐 수도 있다고 했다. 양수, 음수, 0을 반환한다고 하는 것이 정확하다. 여기선 t보다 e가 앞에 나왔으므로 양수가 출력되는 것이 맞았다. 

 

4-3. strcpy()

 

문자열을 복사하는 함수다. 

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

int main(void) {  
    char a[20] = "the real programmer";
    char b[20] = "eunjin lee";
    strcpy(a, b);
    printf("복사된 문자열: %s \n", a);
}

a 배열을 b의 배열로 복사해서 집어넣는 로직이다. 여기에서는 a를 출력해 보면 "eunjin lee"가 나온다.

 

4-4. strcat()

 

뒤에 있는 문자열을 앞에 있는 문자열에 합쳐 보았다.

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

int main(void) {  
    char a[20] = "the real programmer ";
    char b[20] = "eunjin";
    strcat(a, b);
    printf("합쳐진 결과 문자열: %s \n", a);
}
[1]    29718 illegal hardware instruction  "/Users/regina/Desktop/study/cstudy/"main

결과는 또 에러였다. 다시 char a[20]을 [30]으로 바꾸고 시도해보았다. 

 

합쳐진 결과 문자열: the real programmer eunjin

잘 나왔다. 

 

4-5. strstr()

 

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

int main(void) {  
    char a[20] = "the real programmer";
    char b[20] = "real";
    printf("찾은 문자열: %s \n", strstr(a, b));
}

긴 문자열에서 짧은 문자열을 찾아 위치를 반환한다. 찾은 주소 값 자체를 반환하기 때문에 단순히 출력만 하면 찾은 이후의 모든 문자열이 반환된다. 

찾은 문자열: real programmer �

 

728x90
Comments