Strings in C Language
C 언어에서 문자열 사용하기
C 언어에서 문자열을 표현하는 특징적인 부분은 다음과 같이 정리할 수 있다
- char 배열 형식으로 문자열을 나타낸다
- 따옴표(" ")를 이용하여 문자열을 생성하면 문자열이 끝나는 부분을 인식하기 위해서 배열의 끝에 널문자( '\0' )가 추가된다
- 문자열을 저장한 배열의 크기는 포함된 문자보다 1바이트 더 크다
- 문자배열 변수는 포인터 상수이므로 배열의 주소를 변경할 수 없다
- 문자배열 형식을 사용하여 문자열을 생성하면 일반 Stack에 문자열이 생성된다
- 포인터를 사용한 문자열은 주소를 변경할 수 있으나 문자열을 구성하는 문자는 변경할 수 없다
- 포인터를 사용하여 생성한 문자열은 Literal Pool에 저장되며 Literal Pool에는 동일한 문자열이 중복되어 생성될 수 없다
- 다른 함수에서 포인터를 사용하여 문자열을 생성한 경우에도 Literal Pool에 저장되므로 문자열의 메모리 주소는 함수가 다르더라도 문자열 내용이 동일하면 그 주소도 동일하다
#include <stdio.h> int main() { char str[] = { 'H','e','l','l','o','\0' }; // 마지막에 널문자('\0')가 필요함 printf("str = %s \n", str); char str2[] = "Hello"; // 이렇게 하면 마지막 널문자('\0')는 자동으로 추가된다 printf("str2 = %s \n", str2); char *str3 = "Hello"; // 포인터를 사용한 문자열 초기화(원소를 변경할 수 없음) printf("str3 = %s \n", str3); str3 = "World"; // 포인터변수에는 재할당 가능함 printf("str3 = %s \n", str3); // World //str2 = "World"; // 오류, 배열은 포인터 상수이므로 재할당 불가 str[0] = 'h'; // 배열의 원소의 값 변경가능 printf("str = %s \n", str); // hello str2[0] = 'h'; printf("str2 = %s \n", str2); //str3[0] = 'h'; // 실행시 오류, 포인터를 사용하여 초기화한 문자열은 그 원소를 변경할 수 없음 return 0; }
자주 사용되는 문자열 함수
#include <stdio.h> #include <string.h> int main() { /* 문자열의 길이 */ char *str = "Hello"; int len = strlen(str); printf("strlen(%s) --> %d \n", str, strlen(str)); // 5 (널문자 비포함) printf("sizeof(%s) --> %d \n", str, sizeof(str)); // 4 char buf[6]; // 메모리 할당, 동적할당 안됨 /* 문자열 복사 */ strcpy(buf, str); //위에서 할당받은 메모리에 문자열을 복사한다 printf("buf = %s \n", buf); /* 문자열 결합 */ char str1[12] = "Hello"; char *str2 = "World"; strcat(str1, str2); // str1이 가리키는 곳에 충분한 메모리가 할당되어 있어야 한다 printf("str1=%s \n", str1); /* 문자열 비교 */ char *name1 = "ALLEN"; char *name2 = "SMITH"; int res = strcmp(name1, name2); // 같으면 0, 왼쪽이 작으면 0 이하의 수, 왼쪽이 크면 0 이상의 수 printf("strcmp(name1, name2)-->%d \n", res); // -1 res = strcmp(name2, name1); printf("strcmp(name2, name1)-->%d \n", res); // 1 name2 = "ALLEN"; res = strcmp(name1, name2); printf("strcmp(name1, name2)-->%d \n", res); // 0 /* 문자열 안에 포함된 특정 문자 검색*/ name1 = "SMITH"; char *p = strchr(name1, 'T'); // printf("%c \n", *p); if (p) { printf("[%s] 안에 %c 문자가 포함됨 \n", name1, 'T'); } /* 문자열 안에서 특정 부분 문자열 검색 */ str = "Hello World"; char *sp = strstr(str, "or"); if (sp) { printf("[%s] 안에 [or] 문자열이 포함됨(~%s) \n", str, sp); } return 0; }
문자열 포인터의 다양한 표현(포인터 자체가 상수인지 원소가 상수인지를 선언하는 방법)
/* C언어에서 포인터로 문자열을 선언하면 문자열을 구성하는 원소가 상수로 취급되어 원소를 변경할 수는 없다. 그러므로 아래의 3행은 모두 동일한 의미를 가진다. 변수 앞에 const 가 오지 않은 표현은 원소만 상수로 취급된다는 의미이다 */
char * str0 = "ABC"; // str0[0] = 'X'; 이건 오류, str0 = "XYZ"; 가능
const char * str1 = "ABC"; // str1[0] = 'X'; 이건 오류, str1 = "XYZ"; 가능
char const * str2 = "ABC"; // str2[0] = 'X'; 이건 오류, str2 = "XYZ"; 가능
/* 결국 위의 표현은 모두 동일하며 문자열을 구성하는 원소가 상수로 취급된다는 의미이다 */
/* 아래의 표현은 변수 이름 앞에 const 가 붙은 경우이므로 포인터 상수를 선언한 것이다. 그리고 포인터로 선언한 문자열은 원래 그 원소가 상수 취급되므로 아래의 2문장도 같은 의미가 된다 */
char * const str3 = "ABC"; //str3[0] = 'X'; str3 = "XYZ"; 둘다 오류
const char * const str4 = "ABC"; //str3[0] = 'X'; str3 = "XYZ"; 둘다 오류
포인터와 배열, 그리고 문자열 포인터의 테스트
#include <stdio.h> void sub(); int main() { printf("포인터와 배열의 차이점 \n"); // 배열변수는 포인터 상수이고 그 원소는 상수가 아니다 int num[] = { 1,2,3 }; //num = { 4,5,6 }; // 배열변수는 포인터 상수이므로 재할당 불가 num[0] = 5; // 배열의 원소는 상수가 아니므로 재할당 가능 printf("num[0]:%d \n", num[0]); // 5 // 포인터는 상수가 아니므로 포인터에 다른 주소 재할당 가능 // 포인터가 가리키는 배열의 원소는 상수가 아니므로 재할당 가능 int * pnum = num; pnum[0] = 1; // 포인터가 가리키는 배열의 원소는 상수가 아니므로 재할당 가능 printf("pnum[0]:%d \n", pnum[0]); // 1 int num2[] = { 4,5,6 }; pnum = num2; // 포인터이나 상수는 아니므로 다른 주소 재할당 가능 printf("sizeof(num):%d \n", sizeof(num)); // 12 printf("sizeof(pnum):%d \n", sizeof(pnum)); // 4 /* 문자열이 아닌 일반배열과 포인터의 차이점 결론 - 배열변수는 포인터 상수로 취급된다는 점이 서로 다르다 - sizeof()연산자로 메모리의 크기를 확인하면 배열은 원소의 전체 크기가 포인터는 원소를 제외한 포인터 변수가 차지하는 메모리의 사이즈인 4가 나온다 */ printf("문자열 포인터\n"); // 문자열 포인터의 경우에는 일반 포인터와 또 다른 면이 있다 char ch[] = "Hello"; // 자동으로 끝에 \0가 추가되는 문자배열 //ch = "World"; // 배열은 포인터 상수이므로 주소 재할당 불가 ch[0] = 'h'; // 배열의 원소는 상수가 아니므로 재할당 가능 printf("ch:%s \n", ch); // hello char* ch2 = "Hello"; ch2 = "World"; // 포인터 자체는 상수가 아니므로 다른 주소 재할당 가능 // Literal Pool에 저장된 문자열은 원소가 상수 취급되므로 재할당 불가 //ch2[0] = 'w'; printf("ch2:%s \n", ch2); char* ch3 = ch; // Stack에 저장된 문자열의 주소를 포인터에 복사한 경우 // ch는 Literal Pool이 아닌 일반 Stack에 생성된 문자배열이므로 재할당 가능 ch3[0] = 'H'; // 가능, Stack에 저장된 문자배열의 원소는 상수가 아니다 printf("ch3:%s \n", ch3); // 포인터를 사용하여 생성한 문자열은 Literal Pool에 저장된다 // Literal Pool에 저장된 문자열의 원소는 상수로 취급된다 // Literal Pool에는 동일한 문자열이 중복되어 생성되지 않는다 // 그러므로 아래의 선언은 모두 동일한 의미이고 문자열의 주소도 동일하다 char* str1 = "Hello"; char* str2 = "Hello"; const char* str3 = "Hello"; char const * str4 = "Hello"; printf("str1에 할당된 주소:%p\n", str1); printf("str2에 할당된 주소:%p\n", str2); printf("str3에 할당된 주소:%p\n", str3); printf("str4에 할당된 주소:%p\n", str4); char* const str5 = "Hello"; printf("str5에 할당된 주소:%p\n", str5); //str5 = "World"; //str5는 자체가 상수이므로 다른 주소 재할당 불가 // 다른 함수에서 생성된 "Hello" 문자열도 그 주소가 위와 동일하다 sub(); //str1[0] = 'A'; // 실행시 오류 //str3[0] = 'A'; // 컴파일 오류 /* 위의 테스트 결과, 아래의 두 문장이 동일한 효과를 나타낸다 char* str; const char* str; 그렇다면 const char는 왜 필요한걸까? 배열을 포인터에 할당할경우에는 const char가 효과를 발휘한다 */ char chArr[] = "This is string"; char* pchArr = chArr; pchArr[0] = 't'; // 문자열을 구성하는 문자를 변경할 수 있음 printf("pchArr:%s \n", pchArr); // 문자열을 구성하는 문자가 상수로 취급됨 const char* chPtr = chArr; //chPtr[0] = 'T'; // 문자열을 구성하는 문자를 변경할 수 없음 return 0; } // 다른 함수에서 선언된 내용이 동일한 문자열의 주소는 동일할까? void sub() { char* str6 = "Hello"; printf("str6에 할당된 주소:%p\n", str6); }