C-Language/Strings

Strings in C Language

Soul-Learner 2016. 11. 13. 13:29

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);
}