2013년 8월 13일 화요일

C의 문자와 문자열

C에서는 특별히 문자나 문자열에 관한 자료형이 존재하지 않는다. 다만 char 자료형이 8비트이므로 이를 문자나 문자열 자료형을 대신해서 사용한다.

(1) 문자

컴퓨터에서 사용하는 문자는 대부분 고유의 ASCII 코드를 가지고 있다. (물론 요즘은 다국어 지원 등으로 ASCII 외에 유니코드, UTF-8, UTF-16 등의 체계 또한 많이 사용하며 C 표준에도 이를 지원하는 함수가 존재하지만 이 모든 체계들이 기본적으로 ASCII 와 호환이 되도록 설계되었으므로 여기서는 생략하기로 한다.) 예를 들어 문자 'A' 는 65, 문자 '0' 은 48, 문자 '&' 는 38, 줄바꿈(라인피드) 문자는 10 등이다. 이 ASCII 코드가 127을 넘지 않으므로 char로 표현할 수 있는 것이다.

C에서는 모든 문자는 ASCII 코드로 저장한다. 즉, ABCDE 라는 문자 5개를 저장하고 싶다면 char의 배열을 사용하여

char c[5] = { 65, 66, 67, 68, 69 }; // 예 1

라고 하면 된다.

또 하나. 문자 하나에 작은 따옴표로 묶어도 같은 의미이다. 즉

char c[5] = { 'A', 'B', 'C', 'D', 'E' }; // 예 2

는 위의 [예 1]과 같다.

즉 'A' 와 65는 C언어에서는 같은 의미이다.

또한 화면 등으로 표현할 수 없는 문자(줄바꿈 등) 혹은 C언어의 문법상 직접 표현할 수 없는 문자 (따옴표 자체 등) 을 표현하기 위하여 특수문자 개념을 두었다. 특수문자는 역슬래시(\)를 이용하여 표현하며 역슬래시 바로 다음 문자를 보고 판단한다. 예를 들어 다음과 같다.

'\n' : 줄바꿈 문자
'\'' : 작은 따옴표
'\"' : 큰따옴표
'\t' : 가로 탭 문자
'\0' : null 문자
'\\' : 역슬래시 문자

이러한 식으로 거의 모든 문자를 표현할 수 있다.


(2) 문자열

앞서 말했듯이 C언어는 문자열 자료형이 따로 없다. 다만 위에서 약간 짐작은 했겠지만 char의 배열로 문자열을 대신 표현할 수 있다.

C에서 문자열 자료형은 따로 없지만 문자열 자체를 표현하기 위하여 " "(큰따옴표) 를 지원한다. 예를 들어 문자열 ABCDE 를 저장하기 위하여 다음과 같이 정의할 수 있다.

char c[10] = "ABCDE"; // 예 3

그런데 문제가 있다. C언어에서는 문자열이라는 자료형이 따로 없기 때문에 저렇게 char형의 배열을 이용하여 문자열을 저장했다 하더라도 그 문자열의 길이가 얼마인지를 알 수 없다. 그렇다고 배열 c 의 길이(10)를 문자열의 길이라고 할 수도 없다. 하지만 눈으로 봐도 알 수 있듯이 문자열의 길이는 5이며, 또한 10개의 공간에 5개의 문자를 저장하였으므로 저 구문도 전혀 틀리지 않았다.

그렇다면 위의 [예 3] 과 아래 구문과는 같은 것일까?

char c[10] = { 'A', 'B', 'C', 'D', 'E' }; // 예 4

얼핏 보기엔 같아 보인다. (실제로 문자열을 출력해 보아도 같다.) 하지만 실제로는 미세한 차이가 있다.

"ABCDE" 라는 문자열을 문자 하나 하나 풀었을 때 무엇일까?
'A', 'B', 'C', 'D', 'E' 라고 생각할 수 있겠지만, (C 프로그래밍을 하는 분들은 다 알겠지만) 한 문자가 더 들어가야 한다. 바로 '\0' 이라는 NULL 문자이다.

앞서 말했듯이 C언어는 문자열 자료형이라는 것이 따로 없기 때문에 문자열이 어디부터 어디까지인지를 알 수 있는 방법이 딱히 없다. 따라서 문자열의 끝을 알리는 '\0' 문자가 들어가게 된다. ( '\0' 문자의 ASCII 코드는 0 이다.)

따라서 [예 3] 의 경우는 다음과 같다고 보아야 한다.

char c[10] = { 'A', 'B', 'C', 'D', 'E', '\0' }; // 예 5

그런데 [예 3] 과 [예 4] 가 미세한 차이가 있음에도 불구하고 결국 같은 목적을 달성하는 이유는 무엇일까? 그것은 C 언어에서의 배열의 초기화 특성이 있기 때문이다.

char c[10]; 에서 c 는 10개의 char(문자) 값을 저장할 수 있는 배열이며, 원래 초기화할 때에는 10개의 값이 들어가야 한다. 하지만 [예 4] 를 보면 5개의 값만 들어가 있다. 이럴 경우에는 C에서는 나머지 5개의 값에 모두 0을 넣는다. 따라서 결국 [예 5] 와 같은 결과를 갖게 된다. ('\0' 의 ASCII값은 0이라고 앞서 이야기했다.)

[예 3]과 [예 4] 의 미세한 차이가 문제를 발생시키는 경우는 아래의 경우이다.

char c[5] = { 'A', 'B', 'C', 'D', 'E' }; // 예 6 : 문제 없음
char c[5] = "ABCDE"; // 예 7 : 문제??

[예 6]의 경우, 5개의 문자를 저장할 수 있는 배열에 딱 5개가 들어갔다. 이는 옳은 표현이다. (물론 [예 6] 과 같이 저장해 놓고 이를 문자열로 사용할 때에는 문제가 있지만...)
하지만 [예 7]의 경우는 문제가 있는 표현이다. C언어에서는 배열의 크기가 5라고 하여 이를 5개까지만 채우고 나머지를 무시하는 작업을 하지 않는다. 즉,

char c[5] = "ABCDEFG";

라고 한다고 F, G 를 무시하지 않는다는 것이다. F, G 도 어딘가에 저장된다. 그런데 그 저장되는 장소가 어디가될지 불분명하기 때문에 위와 같이 코딩을 하면 프로그램 실행시 치명적인 오류가 발생할 수 있다. (컴파일러는 이 오류를 알려주지 않거나 단지 warning으로만 알려주며, 오류 없이 정상적으로 컴파일은 완료된다.)

다음을 보자.

#include <stdio.h>

int main()
{
    char c[5] = "ABCDEFG";
    char d[5] = "123";

    printf("[%s] [%s]\n", c, d);
    return 0;
}

결과는 다음과 같이 나온다.

[ABCDE123] [123]

의도한 결과와는 다소 다르게 나오게 된다.
위에서 c 를 정의하는 코드를

char c[5] = "ABCDE";

라고 변경해도 같은 결과이다.

따라서 문자열을 저장하거나 다룰 때는 끝에 항상 '\0' 이 있다는 것을 고려하여 '\0' 까지 저장할 수 있는 공간을 마련하여야 한다.

댓글 3개: