C 문자열의 이해 – C언어 강의 6

C 문자열의 이해

메모리 관련 사항

C의 문자열을 이해하는 것은 여러가지로 중요한 의미를 가집니다. 문자열을 취급하는 기술은 어떤 프로그래밍 언어를 사용하건 반드시 숙지해야 하는 부분으로 C언어를 사용하면 가장 원초적인 차원에서 문자열을 이해할 수 있습니다.

그 이유는 C에서 문자열을 사용하려면 메모리의 원리와 사용법을 알아야 하기 때문입니다. C를 제외하고는 그렇게까지 할 필요는 없는데 초창기 C언어의 프로그램의 오류는 대부분 포인터로 인한 메모리 관련 버그 였으며 이후의 프로그래밍 언어를 개발할 때는 프로그래머들이 메모리 관리를 신경쓰지 않도록 만들자는 것이 방침이었다고 말합니다. (*참고 링크)

오늘날 상업적으로 가장 많이 쓰이는 언어중 하나인 자바(Java)의 가장 큰 특징을 말하자만 OOP(객체지향 프로그래밍) 이겠지만 또 하나 중요한 것은 자바에서는 자바가상머신 (JVM)이 메모리 관리를 알아서 해줍니다. 이는 C언어에서 얻은 교훈을 바탕으로 언어의 개발한 것 입니다.

굳이 모든 응용 프로그래머들이 포인터를 사용해서 직접 메모리를 관리할 필요가 있느냐? C언어의 초창기인 1970년대의 하드웨어는 지금에 비해서 성능이 매우 좋지 않아서 그럴 필요가 있었다고 합니다. 사실 성능을 위해서는 어셈블리어가 최상이었다고 하는데 요즘말로 하면 고인물들은 모든 프로그램을 어셈블리어로 작성했다고 합니다.

무어의 법칙에 따라 하드웨어 발달이 가속화되자 고수준 언어(high level language)인 C언어로도 충분히 성능을 낼 수 있게 되자 개발속도가 빨라졌을 것을 짐작할 수 있죠. 어셈블리어는 말이 어셈블리어지 기계어에 가까운 저레벨 프로그래밍 언어로 똑같은 프로그램을 짜더라도 시간이 더 걸립니다.

당시 컴퓨터의 성능을 지금과 비교해본다면 컴퓨터 한대를 여러개의 단말기에 연결하여 유닉스 운영체제에서 타임쉐어링(time sharing)하는 방식이었습니다. 유닉스를 PC에 구현한 리눅스도 기본적으로 다중 사용자에게 서버 역할을 합니다. 한대의 서버가 다중 클라이언트의 요청을 받아 처리하는 인터넷의 서버와 구조가 비슷하죠.

유닉스는 벨연구소의 데니스 리치와 켄 톰슨이 C언어로 개발했고 리눅스는 리누즈 토발즈가 유닉스를 모델로 역시 C언어를 사용하여 오픈소스로 개발했습니다.

C 문자열 (문자와 차이)

문자와 문자열은 무슨 차이가 있는지 알아보겠습니다.

C의 문자형은 char 타입으로 메모리 1바이트를 사용합니다.

char myChar = ‘a’; 이것은 문자 ‘a’에 해당하는 아스키코드를 myChar 라고 이름을 붙인 char 형 변수에 저장합니다.

한편 문자열은… C에서는 문자열에 해당하는 원시 자료타입은 없습니다. 즉 int 나 char 같은 타입이 없다는 말인데요. 다른 언어에서는 String 같은 자료형이 있는데 C언어에서 없는 이유가 있습니다.

C언어에서는 char 형 배열을 문자열로 사용하기 때문입니다. “a” 이렇게 큰 따옴표를 찍으면 컴파일러는 문자열로 인식합니다. 문자와 같아 보이지만 실제로는 다릅니다. 문자열은 마지막에 널문자 \0 가 들어갑니다. 아스키 코드는 0입니다. char myString[10] = “Hello”; 라는 문자열을 예로 들면 ‘H’ ‘e’ ‘l’ ‘l’ ‘o’ 의 다섯개의 char형과 하나의 널문자 \0 까지를 문자열로 취급합니다. “a” 도 ‘a’ 와 \0 널문자의 문자열로 구성됩니다.

이는 쉽게 말하면 컴파일러가 널문자를 끝에서 읽으면 그 뒤에 있는 것은 무시한다는 말입니다. 그러면 배열의 크기가 myString[10] 처럼 10바이트인데 저 “Hello”는 6바이트만 사용합니다. 나머지 4바이트는 어떻게 되나요?

4바이트는 그대로 메모리를 차지하고 있습니다. 이것을 예제를 통해 알아보겠습니다.

문자열 예제

sizeof 연산자는 배열의 전체 크기를 알아내는데 사용합니다. strlen 함수는 string.h 에 프로토타입이 정의되어 있는데 이 함수는 실제 사용중인 문자열의 크기를 알아냅니다.

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

#define SIZE 30

int main(void){


    char myString[SIZE] = "Hello C Language!";
    int fullSize = sizeof myString;
    int realSize = strlen(myString);

    printf("-> myString : %s\n", myString);
    printf("-> full size : %d\n", fullSize);
    printf("-> real size : %d\n", realSize);


    for (int i = 0; i < realSize; i++)
    {
        printf("[%02d] -> %c (%d)\n",i, myString[i], myString[i]);
        /* code */
    }
    
    return 0;
}
-> myString : Hello C Language!
-> full size : 30
-> real size : 17
[00] -> H (72)
[01] -> e (101)
[02] -> l (108)
[03] -> l (108)
[04] -> o (111)
[05] ->   (32)
[06] -> C (67)
[07] ->   (32)
[08] -> L (76)
[09] -> a (97)
[10] -> n (110)
[11] -> g (103)
[12] -> u (117)
[13] -> a (97)
[14] -> g (103)
[15] -> e (101)
[16] -> ! (33)

코드의 결과를 보면 배열의 full size는 초기 할당과 같은 30입니다. real size 는 이 30 중에서 실제로 사용한 크기를 의미합니다. 널문자의 바로 앞까지 카운팅합니다. 아스키 코드를 보면 공백문자도 32로 할당이 되있는 것을 알 수 있습니다.

참고로 위에서 나온 #define 은 상수를 지정할 수 있습니다. 결과는 for 루프를 사용하여 문자열의 요소인 문자를 출력합니다.

문자열 상수와 비교

define 으로 문자열 상수를 만들 수 있습니다. define 키워드는 전처리기의 하나로 컴파일시 지정한 단어를 문자열로 대체합니다. 문자열 뿐 아니라 숫자, 수식 등을 상수로 만들 수 있습니다.

아래 예제에서는 sizeof 연산자로 얻는 전체의 크기가 strlen으로 얻은 문자열의 크기보다 1개 더 많습니다. 이는 문자열 상수에 널문자열이 추가되기 때문입니다.

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

#define SIZE 30
#define STRING_VAR "String Variable"

int main(void){

    char name[] = "Jordan";

    printf("1. char array");

    printf("-> name : %s\n", name);
    printf("-> full size : %zd\n", sizeof name);
    printf("-> real size : %zd\n", strlen(name));


    printf("2. define constant");

    printf("-> STRING_VAR : %s\n", STRING_VAR);
    printf("-> full size : %zd\n", sizeof STRING_VAR);
    printf("-> real size : %zd\n", strlen(STRING_VAR));
    
    return 0;
}
1. char array-> name : Jordan
-> full size : 7
-> real size : 6
2. define constant-> STRING_VAR : String Variable
-> full size : 16
-> real size : 15

요약

C 문자열의 이해를 통해서도 배열과 메모리의 개념을 습득할 수 있습니다. C언어의 학습자는 문자열을 사람의 입장에서 보는게 아니라 기계의 입장에서 보는 시각도 길러야 합니다.

C언어는 설명하는 사람의 관점에 따라 고수준 언어(high level language)라고 말하기도 하고 저수준 언어(low level language)라고도 말합니다. 그럴 수 있는 이유는 C언어는 고수준과 저수준을 자유롭게 이동할 수 있기 때문입니다.

C언어의 입문자는 시간이 걸리더라도 하나씩 하나씩 소소코드를 입력하여 결과를 확인하면서 나아가는 것이 바람직한 방법입니다. C언어는 배우는데도 시간이 걸리지만 그럴듯한 프로그래밍을 제작하는데 까지는 더 오랜 시간이 소요됩니다. 어떤 화려한 결과물을 빨리 내려면 최근에는 자바를 비롯하여 적합한 언어들이 많이 있습니다.

하지만 C언어의 장점은 컴퓨터의 원리와 구조 자체를 배우는 일이기 때문에 프로그래머의 길을 멀리까지 바라보고 접근하는 것이라면 충분히 가치가 있습니다.

참고 링크

브라이언 커니핸 C Programming Language

외부참고 문서

씹어먹는 C 언어 – <15 – 1. 일로와봐, 문자열(string)>

C 문자열 / 배열

Leave a Comment