프로그램과 우리 사이, 대화가 필요해 I
문자열 처리는 가히 IT(Information Technology: 정보기술)의 핵심이라 말할 수 있습니다.
일반적으로 정보와 데이터를 나누는 기준에 대해서 많은 사람들이 이렇게 말합니다. "데이터의 광산에서 지금 내게 필요한 것을 찾아 가공한 것이 정보이다." 정말 그래서일까요? 처음엔 각기 다양한 서비스로 시작된 인터넷 사이트들이 portal을 표방하며 현재는 검색에 목숨을 걸고 있다고 해도 과언이 아닙니다. 구글이 어떻게 유명해 졌는지 우리는 잘 알고 있습니다. 그러면 우리가 검색할 때 어떻게 합니까? 당연히 문자열로 입력합니다. 검색 결과는 모두 어떻게 나오죠? 당연히 문자열로 나옵니다. 그뿐이 아닙니다. 우리가 사용하는 대부분의 프로그램들이 입출력 시에 문자열을 사용합니다. 심지어 무선 단말기로 전화를 걸 때 입력하는 전화번호도 문자열이란 것을 알아야 합니다. 한국에서는 오늘 하루 동안도 수없이 많은 문자 메시지들이 까똑까똑거리며 허공을 날아 다녔습니다. 당연히 문자열입니다. (더 엄밀히 말하자면 바이트들의 나열이긴 합니다.) 아주 오래 전(?)에는 문자열 처리 전용 언어가 존재했을 정도로 문자열 처리는 매우 중요한 문제였습니다. 더욱이 프로그램과 사용자가 대화하는 가장 일반적인 방법은 문자열로 구성된 메시지를 주고 받는 것입니다. 요즘은 프로그램끼리도 메시지를 주고 받습니다. 그래서 현재는 정규표현 식 등 문자열을 위한 다양한 라이브러리가 만들어지고 있으며 각 프로그래밍 언어들도 문자열 처리를 위한 많은 함수들을 기본적으로 제공하고 있습니다. 먼저 C언어 에서 제공하는 문자열의 세가지 형태를 살펴보고 문자열을 활용하는 다양한 함수들을 알아보도록 하겠습니다.
큰 따옴표("")로 감싸 안은 문자열을 의미하며 이 큰 따옴표는 그 동안 우리가 잘 알지 못했던 파워와 포스를 몇 가지 지니고 있습니다. May the force be with you!!
1) 큰 따옴표 문자열은 메모리를 스스로 할당합니다.
뿐만 아니라 할당된 메모리 공간의 시작 주소를 알려줍니다.
아래 문장들은 모두 아무 문제없이 컴파일 되고 실행도 됩니다.
printf("address : %u\n" , "Sinclair") ;
putchar("Sinclair"[3]) ; // 배열 연산자의 두 피 연산자 주소와 정수
puts("Sinclair" + 3) ;
2) 이미 존재하는 같은 문자열이라면 대부분의 경우 다시 또 할당하지 않습니다.
그렇기 때문에 일반적으로 문자열 내의 모든 문자는 상수문자들입니다.
char * str = "Sinclair" ;
char * name = "Sinclair" ;
printf("%u %u %u\n" , "Sinclair" , str , name) ;
만약에 name 문자열 중 한 글자를 바꾸고 싶다고 name[0] = 'D' ; 라는 수식을 사용하면 컴파일은 되지만 실행에러가 발생합니다. 같은 문자열을 두 번 만들지 않았기 때문에 name을 통해 바뀐 것이 나머지 모든 문자열에도 원하지 않은 영향을 주게 됩니다. 예전에 MS-DOS시절의 turbo C 컴파일러에서는 새로운 상수 문자열을 다시 할당하는 방법으로 바꿀 수 있도록 해줬던 적이 있었습니다.
3) 인접한 두 개 이상의 상수 문자열은 하나의 문자열로 동작합니다.
puts("Sinclair" // 다음 문장과 연결을 원하면 '\'를 넣는 것이 원칙
" is coooooooooooooool") ;
// Sinclair is coooooooooooooool
putchar("Sin" "clair" [4]) ; // 'r'이 아닌 'l'이 찍힌다.
문자열 포인터도 포인터로서의 속성을 그대로 가지고 있습니다. 그렇기 때문에 포인터로 선언된 문자열을 일반 수식에서는 마치 배열인 것처럼 사용할 수 있습니다. 뿐만 아니라 배열이 아니므로 중간에 대입연산자를 사용하여 문자열을 통째로 바꿀 수 있습니다. 다만 상수문자열의 주소 값을 그대로 가져왔기 때문에 문자를 변경할 수 없는 상수 문자열의 속성도 가지고 있습니다. 그리고 한가지 더 주의해야 할 것은 포인터 문자열을 배열이나 동적 할당 함수로 메모리 할당이 안된 상태로 사용하는 것은 때로 아주 위험한 결과를 가져오기도 합니다. 아무 문제없이 컴파일이 된다고 장땡은 아닙니다. 컴파일 에러는 대부분 친절하게도 어떤 에러라고 알려주고 고칠 수 있는 여지를 남겨주지만 대부분 실행 에러는 그냥 프로그램이 종료해버리고 맙니다. 간혹 더 치명적인 경우 기기 자체가 꺼지기도 합니다.
name1st = "Juliet" ; // 이것은 가능하지만,
name1st[2] = 'x' ; // 이것은 불가능합니다.
printf("address - %u : %u : %u\n" , "Sinclair" ,
name1st , & name1st) ;
printf("sizeof - %d %d %d\n" , sizeof("Sinclair") ,
sizeof name1st , strlen(name1st)) ;
// the result on 32bit OS
/*
address - 4202496 : 4202496 : 2280668
sizeof - 9 4 8
*/
문자열 배열도 배열로서의 속성을 그대로 가지고 있습니다. 일반적으로 사용할 때 수식을 포인터로 바꿔서 사용해도 되지만 그럼에도 불구하고 반드시 배열임을 기억해야 합니다. 중간에 대입연산자를 사용하여 문자열을 통째로 바꿀 수 없기 때문에 문자열 처리 함수를 사용해야 합니다. 지난 chapter에서 우리는 배열이 메모리 할당을 한다고 배웠습니다. 그렇게 때문에 새로 할당된 새로운 메모리 공간(주로 stack)에 상수 문자열의 주소 값이 아닌 각 문자들을 하나, 하나 배열로 복사해옵니다.
name2nd = "Juliet" ; // 이것은 불가능하지만,
// 보이나요? name2nd 앞 뒤에 &와 [0]이 있는 것이~
name2nd[2] = 'x' ; // 이것은 가능합니다.
printf("address - %u : %u : %u\n" , "Sinclair" ,
name2nd , &name2nd) ;
printf("sizeof - %d %d %d\n" , sizeof("Sinclair") ,
sizeof name2nd , strlen(name2nd)) ;
// the result on 32bit OS
/*
address - 4202496 : 2280640 : 2280640
sizeof - 9 10 8
*/
문자열을 말할 때 널문자 == '\0', 코드값 0 을 빼놓고 얘기 할 수 없습니다. 일반적인 문자들로 이뤄진 문자배열을 문자열로 만드는 존재가 바로 코드값 0, 널문자입니다. 가장 먼저 나오는 널문자 까지만 문자열로 생각합니다. 실제로 문자배열을 char string[10]으로 잡았다 하더라도 그 배열에 널문자를 제외한 문자만 들어 있다면 사이즈 10을 넘어선 공간에서 처음 나오는 널문자 까지를 문자열이라고 생각합니다. 때문에 C언어에서 문자열을 만들거나 처리할때 항상 널문자를 염두에 두어야 합니다.
#Sinclair #씽클레어 #싱클레어 #씽클레어도씨 #씨언어 #씨프로그래밍 #C언어 #Cprogramming #C_Programming #C #Programming #Clanguage #C_Language