반응형

문자열(String)

 

※ 문자열 ※

 

문자열은 연속된 문자들을 모아둔 것이다.

 

인간은 텍스트를 사용하여 정보를 표현하고 저장하므로 문자열은 프로그램에서 중요한 위치에 있다.

 

문자열은 특수 문자이거나, 공백 문자이거나, 어떤 문자라도 큰 따옴표로 둘러싸인다.

 

즉, 쉽게 말해 큰 따옴표로 둘러싸인 것은 문자열이 된다.

 

그리고 문자열도 프로그램에 필요한 데이터이므로 메모리에 저장된다.

 

또한 큰 따옴표와 작은따옴표를 잘 구분하도록 한다. 

 

작은 따옴표는 문자에 쓰고 큰 따옴표는 문자열에 쓴다.

 


 

※ NULL 문자 ※

 

NULL 문자는 아스키코드 값이 0인 문자이다.

 

문자열은 반드시 NULL로 끝나야 하는데 그 이유는 문자열의 끝을 NULL 문자로 

 

표시하지 않으면 문자열의 끝을 발견하는데 상당한 애로가 있기 때문이라고 한다.

 

본래 NULL문자의 의미는 터미널이나 프린터로 보내지는 NOP(No Operation) 명령어를 의미했다.

 

NULL 문자는 \0 으로 표현하는데 이것은 아스키코드에서 값이 0인 코드를 의미하므로

 

단순히 정수 0으로 쓰기도 한다.

 


 

 

※ 그렇다면 왜 문자열의 끝을 반드시 표시를 해줘야 하는건가? ※

 

본재 정수형 변수의 경우는 끝을 표시할 필요가 없었다. 그 이유는

 

정수형 변수는 사용되는 바이트의 개수가 항상 일정하기 때문이었다.

 

보통 정수형 변수의 경우 4바이트가 할당된다.

 

But 문자열의 경우 문자열을 저장하기 위해 

 

10바이트의 문자 배열을 잡고 NULL 문자를 사용하지 않는다고 가정할 때

 

문자열 "hello"를 이 문자 배열에 저장하면 문자들을 저장하기 위해 5바이트가 사용된다.

 

그러면 나머지 5바이트는 사용되지 않는다. 이렇게 되면 나머지 

 

5바이트에는 쓰레기 값이 들어 있을 수 있다.

 


 

 

※ NULL 문자의 필요성 ※

 

NULL 문자는 정상적인 데이터와 쓰레기 값을 구분하기 위해 필요하다.

 

만약 "hello#$(#@" 이라고 출력되었을 때 사람은 

 

"hello" 까지가 정상적인 데이터(문자열)인줄 알지만,

 

프로그램은 어디서부터 어디까지가 정상적인 데이터인지 알지 못한다. 

 

그이유는

 

"#$(#@" 이것들도 각각 의미 있는 하나의 문자이고 문장열이다. 

 

때문에 문자열의 경우, 문자열의 끝을 표시하여야만 

 

메모리에서 정확히 어디까지가 의미있는 문자열인지 확실하게 할 수 있다.

 


 

 

※ 왜 하필 NULL 문자인가? ※

 

문자를 사용해서 문자열의 끝을 표시하려면 절대 문자열에 사용되지 않는 문자여야 했다.

 

왜냐하면 그 문자를 만나면 문자열의 끝이라고 알 수 있도록 해야 했기 때문이다.

 

그래서 아스키코드에서 0의 값을 가진 NULL 문자가 딱 조건에 맞는 문자였다.

 

프로그램이 NULL문자를 만나면 그 전까지가 정상적인 문자라고 인식할 수 있기 때문이다.

 

그래서 항상 문자열을 담을 배열을 선언할 때는 담을 문자열의 길이보다 1이 더 많게 길이를 써주는 것이다.

 


 

 

※ 큰 따옴표와 작은따옴표의 주의점 ※

 

'A'와 "A"의 차이점은 분명하다.

 

'A'는 하나의 문자를 나타내고, "A"는 문자열이며 큰 따옴표로 묶인 것은 뒤에 NULL 문자가 추가된다.

 


 

 

※ 문자 배열의 초기화 ※

 

보통 배열을 초기화하듯이 각 배열 원소들의 값들을 { 와 } 문자 안에 넣어서 초기화할 수 있다.

 

문자 배열의 초기화 시에는 마지막 원소에 NULL 문자를 의미하는 \0을 넣어주어야 한다. 

 

그리고, 문자 배열의 크기는 초기와 문자열의 길이보다 커도 된다.

 

들어갈 문자가 6개 여도 배열의 크기는 11이어도 된다.

 

또한 문자 배열에 문자열을 넣을 때에는 다음 코드와 같이 할 수 있다.

 

char str[11] = "qwerdf";

 

허나, 만약에 문자 배열의 크기가 충분하지 않으면 컴파일러는 경고를 한다.

 

그리고 일부 문자는 저장되지 않으며 NULL 문자도 추가되지 않는다.

 

char str[3] = "qwerdf";

 

반대로 아까 설명했듯이 문자 배열의 크기가 초기화할 문자열보다 크다면 걱정 안 해도 된다.

 

남는 공간은 모두 NULL 문자로 초기화되기 때문이다.

 

char str[11] = "qwerdf";

 

그리고 NULL 문자로 초기화되는 것은 이미 배열 부분에서 다들 배웠을 거리라 생각된다.

 

"배열의 초기화에서 초기값이 없는 공간은 모두 0으로 초기값이 정해진다"라고 말이다.

 

그리고 만약 배열을 NULL 문자열로 초기화하려면 다음과 같이 코드를 사용하면 된다.

 

char str[11] = "";

 

위의 코드처럼 해주면 모두 \0이 저장된다.

 

그리고 마지막으로 만약 배열의 크기를 지정하지 않으면 

 

컴파일러가 자동으로 배열의 크기를 초기화 문자열에 맞추어 준다.

 

이렇게 배열의 크기를 저장하지 않아도 자동으로 크기를 맞추어 주는 것은 배열에서 배웠을 것이다.

 

char str[] = "qwer";

 


 

 

※ 문자열의 출력 ※

 

문자 배열에 저장된 문자열을 출력하라면 반복문을 이용해서 각 배열 원소를 출력해도 된다.

 

for(i=0; i<4; i++)

printf("%c", str[i]);

 

But, 보다 더 편리한 방법은 %s 형식 지정자를 사용하는 것이다.

 

%s를 이용해서 printf함수를 쓸 때는 문자 배열의 이름을 인수로 주면 된다.

 

printf("%s",str);

 

또한, 문자열의 경우 사용할 수 있는 방법은 형식 지정자 없이 그냥 문자 배열을 전달하는 것이다.

 

printf(str);

 


 

 

※ 문자열의 변경 ※

 

문자 배열에 들어있는 문자열은 변경이 가능하다.

 

예를 들어 "hello"라는 문장로 초기화되어있다고 해도, 뒤에 "world"로 변경이 가능하다.

 

방법은 여러 가지가 있다.

 

1. 각각의 문자 배열 원소에 원하는 문자를 개별적으로 대입하는 방법. 확실하지만 매우 불편하다.

그리고 이 경우 프로그래머가 문자열 끝에 NULL 문자를 넣어줘야 한다.

char str[11] = "hello";

str[0] = 'w';

str[1] = 'o';

str[2] = 'r';

str[3] = 'l';

str[4] = 'd';

str[5]= '\0';

 

2. 라이브러리 함수인 strcpy()를 이용하여 문자열을 문자 배열에 복사할 수 있다.

문자 배열에 원하는 문자열을 넣을 수 있으므로 가장 편리한 방법이다.

또한 strcpy 함수를 쓸려면 string.h 헤더 파일을 포함해줘야 한다.

 

char str[11] = "hello";

strcpy(str, "world");

 

3. 가장 편리할 것 같지만, 사용할 수 없는 방법이다.

char str[11] = "hello";

str = "world";

 

3번의 방법은 배열 이름 str에 문자열의 주소를 대입하는 문장으로 문법적 오류이다.

 

왜냐하면 str은 배열의 이름이자 문자 배열의 시작 주소를 의미하며 배열도 포인터이기 때문이다.

 

즉, 위 3번의 코드는 컴파일러 입장에서 배열의 시작 주소를 가리키는 

 

배열의 이름에 문자열을 대입하라라는 의미이기 때문에, 주소에 문자열을 넣을 수는 없다.

 


 

 

※ 문자열 상수와 포인터 ※

 

문자열 상수는 "hello world"와 같이 프로그램 소스 코드 안에 포함된 문자열을 의미한다.

 

문자열 상수는 프로그램이 사용하는 메모리 영역 중에서 

 

텍스트 세그먼트(text segment)라고 불리는 특수한 메모리 영역에 저장된다.

 

텍스트 세그먼트는 "읽기는 가능하지만", 우리가 "변경할 수 없는" 메모리 영역이다.

 

다음의 코드를 해석해보도록 한다.

 

char *p = "Hello World";

 

해석

 

문자형을 가리키는 포인터 변수 p가 선언되었다.

 

포인터를 포함한 모든 변수는 데이터 세그먼트(Data segment)라고 불리는 영역에 저장된다.

 

따라서 포인터 변수 p도 데이터 세그먼트에 생성된다.

 

데이터 세그먼트는 우리가 값을 "변경할 수 있는" 메모리 영역이다.

 

문자열 상수 "Hello World"가 저장된 주소가 포인터 변수 p에 저장된다.

 

위를 정리해보자면

 

"hello world"와 같은 문자열 상수는 텍스트 세그먼트라는 메모리 영역에 담기는데

 

그 메모리 영역은 읽기만 가능하고 우리가 변경할 수는 없다.

 

하지만 모든 변수는 데이터 세그먼트라는 메모리 영역에 담기는데 

 

그 메모리 영역은 읽기 및 쓰기가 가능하므로 값을 변경이 가능하다.

 

따라서 다음의 코드를 보고 이해해보도록 하자

 

명심해야 할 건 데이터 세그먼트는 변경이 가능하고 텍스트 세그먼트는 읽기만 가능하다는 거다.

 

char *p = "Hello World";

strcpy(p, "Goodboy");

 

 위 코드는 포인터 변수 p에 "Hello World"이라는 문자열을 담고 있는 텍스터 세그먼트라는 

 

메모리 영역에서의 메모리 주소를 담고 있다.

 

따라서 포인터 변수 p에 "Goodboy"이라는 문자열을 넣으려 하면 

 

포인터 변수 p에 쓸 공간도 없을뿐더러, 텍스트 세그먼트에 있는 문자열 상수의 주소를 

 

다른 문장열 상수의 주소로 바꾸려고 했기 때문에 실행 오류가 난다.

 

 

그렇다면 다음 코드를 봐보자

 

char *p = "Hello World";

p = Goodboy

 

위 코드는 데이터 세그먼트에 있는 포인터 변수 p에 문자열 상수를 담고 있는 

 

텍스트 세그먼트 영역에 있는 문자열 상수의 주소를 담고 있는데,

 

포인터 변수 p가 값이 변경 가능한 데이터 세그먼트에 있으므로 

 

포인터 p에 담긴 시작 주소를 바꿀 수 있는 것이다.

 

쉽게 정리하자면,

 

strcpy는 문자열을 복사하는 함수인데, 인자 값으로 포인터 변수가 넘겨지면

 

포인터 변수의 값을 바꾸는 게 아니라 해당하는 포인터 변수에 저장되어 있는

 

"읽기만 가능한 텍스트 세그먼트 메모리 영역의 주소"를 새로운 문자열 상수의 주소로

 

복사를 시도하니깐 오류가 나는 것이다. 한마디로 읽기만 가능한 메모리 영역 주소를 새로 쓰려고 

 

하니깐 쓰기 오류가 나서 실행이 안 되는 거다.

 

그렇다면 포인터 변수에다가 직접 문자열 상수를 담는 것은 왜 될까?

 

답은 간단하다.

 

포인터 변수도 변수이기 때문에 쓰기도 가능한 영역인 데이터 세그먼트에 생성되기 때문에,

 

포인터 변수 "p의 값"을 새로운 문자열 상수의 주소로 바꿔도 실행이 가능하다.

 

결론은 쓰기가 가능한 데이터 세그먼트 영역에 있는 것의 값을 바꾸나, 아니면

 

쓰기가 불가능한 텍스트 세그먼트 영역에 있는 것의 값을 바꾸나 문제있은 것 같다.

반응형

+ Recent posts