본문 바로가기

C언어

Chapter 4. 문자열과 포인터

Chapter 4. 문자열과 포인터

C언어는 문자열을 문자 배열로 정의한다.

a,'a', "a"의 차이

문자열은 반드시 NULL 문자로 끝나도록 정해져 있으므로"a"는 '\0'을 포함하고 있다.

4_1.c

#include <stdio.h>

main()
{
char *imsip;
char imsi;

imsip = 'a'; (1)
imsip = "a"; (2)

imsi = 'a'; (3)
imsi = "a"; (4)
}

gcc -o 4_1 4_1.c
4_1.c: In function ‘main’:
4_1.c: 8: warning: assignment makes pointer from integer without a cast
4_1.c: 12: warning: assignment makes integer from pointer without a cast

"a"는 문자열이며 문자열은주소 값을 리턴하므로 "a"가 저장된 곳의주소가imsip에 할당되는 것은맞고, 정수 변수imsi에 할당되는것틀리다.

문자열 포인터 변수

문자열 포인터 변수에 저장되는 값은 주소가 된다. 이 주소가 가리키는 대상체가 문자열일 때 이것을 문자열 포인터 변수라고 지칭한다.

사실 이것은 정확한것은 아니다. 문자열포인터 변수가 가리키는 대상체는 문자열 자체가 아니라 문자열이 저장된 곳의 가장 첫번째 문자의 위치를 가리키기 때문.

char*string;

string ="archie";

위의 연산식을 만나는 순간 컴파일러는 "archie"중에서 'a'가 저장된 곳의 주소를 string에 할당한다.

"archie"는 문자열상수이다. 절대 다른 문자열로 바뀌지 않는다.

포인터를 이용한 문자열 조작

4_2.c

#include <stdio.h>

main()
{
char *string;
string = "archie";

//printf("%c\n", *(string+0));
//printf("%c\n", *(string+1));
//printf("%c\n", *(string+2));
//printf("%c\n", *(string+3));
//printf("%c\n", *(string+4));
//printf("%c\n", *(string+5));

for(;*string;string++)
printf("%c\n", *string);

}

gcc -o 4_2 4_2.c
./4_2
a
r
c
h
i
e

문자열 출력 방식

printf("%s\n", string); // archie

printf("%s\n", string+1); // rchie

printf(string); // archie

printf(string+2); // chie

puts(string); // archie

puts(string+3); // hie

문자열을 출력한후 개행이 필요하다면'printf'보다'puts'이메모리를 적게 잡아먹는함수로 훨씬 가볍다. puts() 애용!!

착각하기 쉬운 첨자

4_3.c

#include <stdio.h>

main()
{
char *string = "archie";

printf("%s\n", &string[3]);

printf("%s\n", &3[string]);

puts(string+3);
}

gcc -o 4_3 4_3.c
./4_3
hie

hie

hie

string[3]은 포인터 변수를 배열처럼 사용했을 뿐 배열이 아니다.

포인터 변수에서 첨자를 사용한다는 것은 단지 가리키는 포인터에서 얼마만큼떨어져 있느냐에 대한 설명일 뿐이다.

C언어에서의[]는 연산자이다. 두 개의 피 연산자를 가지며 하나의 피 연산자는 기본 위치를, 다른 피 연산자는 기본 위치에서얼마만큼 떨어져 있는지를 나타낸다.

*(string + 3) == string[3] ==*(3 + tring) == 3[string]

*string과 string[]의 차이

4_5.c

#include <stdio.h>

main()
{
char *string = "archie"; // (1)
char dim[] = "dimention"; // (2)

printf("string : %#010x\n", string);
printf("&string : %#010x\n", &string);
printf("dim : %#010x\n", dim);
printf("&dim : %#010x\n", &dim);
printf("&dim[0] : %#010x\n", &dim[0]);
}

gcc -o 4_5 4_5.c
./4_5
string : 0x080484c0
&string : 0xbfb3e610
dim : 0xbfb3e606
&dim : 0xbfb3e606
&dim[0] : 0xbfb3e606

string은 포인터 변수이기 때문에 메모리 할당을 받고 dim은 배열명이기 때문에 메모리 할당을 받지 않았다.

그래서 dim과&dim, &dim[0]의 주소 값이 동일하다.

dim이 메모리를 할당 받지 않은 증거는 포인터 연산에서도 찾아볼 수 있다.

string++

dim++;

주소 더하기 정수는 주소다.

dim++ 수식은 dim = dim + 1 의 수식이며 dim + 1은 문제가 없는 정상적인 수식이다.

하지만 위에서 말한것처럼 "주소 더하기 정수" 수식으로 주소값이 되며 이를 다시 dim 배열명 자체에 넣으려고 하고 있다.

dim은 배열명일 뿐이지변수가 아니다.

4_6.c

#include <stdio.h>

main()
{
char *string = "archie";
char dim[] = "dimention";
int i;

for(;*string;string++)
putchar(*string);

putchar('\n');

/*for(;*dim;dim++)
putchar(*dim);*/

for(i=0;dim[i];i++)
putchar(dim[i]);

putchar('\n');
}

gcc -o 4_6 4_6.c
./4_6

archie
dimention

※ 출력함수의 메모리 사용 : printf() > puts() >putchar()

문자열 상수변경

4_8.c

#include <stdio.h>

main()
{
char *string = "archie";
*string = 'T';
}

gcc -o 4_8 4_8.c
./4_8
세그멘테이션 오류

string이 가리키고 있는 주소는큰 문제가 없다.

하지만, string이 가리키고 있는 주소에 들어있는 값은 변수가 아니라 상수라는 것이 문제다.

바로 "문자열 상수"인 것이다.변하지 않는 상수를 억지로'T'라는 문자를 할당하여 "Trchie"라고 변화시키려고 하고 있다.

4_9.c

#include <stdio.h>

main()
{
char string[] = "dimention";
string[0] = 'T';
puts(string);
}

gcc -o 4_9 4_9.c
./4_9
Timention

배열에서string[0] = 'T'의의미는string[0]에 'T'문자를 할당하는 것이다.

"archie"라는 문자열 상수를 바꾸려는 것이 아니라 배열 요소 하나를 변경한 것이다.

문자열과 포인터 배열

4_10.c

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

main()
{
int i;
char name[5][20];

strcpy(name[0], "Jung jae Une");
strcpy(name[1], "Han Woo Ryong");
strcpy(name[2], "Byun Ji Ha");
strcpy(name[3], "Lee Do Geun");
strcpy(name[4], "Hong Jae Mok");

for(i=0;i<5;i++)
puts(name[i]);
}

gcc -o 4_10 4_10.c
./4_10
Jung jae Une
Han Woo Ryong
Byun Ji Ha
Lee Do Geun
Hong Jae Mok

name은 2차원 배열이기 때문에 name[0]은 0번째 행을 가리키는 주소가 된다.

이 주소 값에 해당하는 영역부터 한 글자씩 문자여을 복사하는 것이다.

하지만, 위의 프로그램은 상당히 큰 단점을갖고 있다.

각각의 배열 요소가 20byte씩할당이 되어 있기 때문에 메모리 낭비가 발생할 수 있고, 20byte를 넘는 문자열일경우 배열을 재 정의해야 할 것이다.

이와같은 문제를 해결할 수 있는 것이 포인터 배열이다.

배열을 정의하고 문자열을 할당할때는 아래와 같이 사용하지 않는다는 것을 잊지 말아야 한다.

char name[5][20];

name[0] = "Jung Jae Une";

"Jung Jae une"이라는 문자열 중에서 'J'가 저장된 위치를 name[0]에 할당하려는 수식이다.

name이라는 배열명이 배열 상수인 것처럼 name[0]는 변수가 아닌 상수이다.

따라서, 변수가 아니기 때문에 주소 값을 할당 할 수 없다.

포인터 배열에 문자열할당

4_11.c

#include <stdio.h>

main()
{
int i;
char *name[5];

name[0] = "Jung jae Une";
name[1] = "Han Woo Ryong";
name[2] = "Byun Ji Ha";
name[3] = "Lee Do Geun";
name[4] = "Choi Jae Mok";

for(i=0;i<5;i++)
puts(name[i]);
}

gcc -o 4_11 4_11.c
./4_11
Jung jae Une
Han Woo Ryong
Byun Ji Ha
Lee Do Geun
ChoiJae Mok

name은 포인터를 저장할 수 있는배열이다.

포인터 변수에 각각 'J', 'H', 'B', 'L', 'C' 가저장된 곳의 주소를 할당하고 있다.

char *name[5]로 정의하면name[0] ~ name[4]가 메모리에 할당되며 각각은 주소를 저장할 수 있다.

하지만, name이라는 포인터 배열명은 절대 메모리가 할당되지 않는다.

printf("%#010x\n", name);

printf("%#010x\n", &name);

배열명은 상수이므로같은 값이 출력된다.

printf("name[0] : %#010x\n", name[0]); // 0x080484f0
printf("name[1] : %#010x\n", name[1]); // 0x080484fd
printf("name[2] : %#010x\n", name[2]); // 0x0804850b
printf("name[3] : %#010x\n", name[3]); // 0x08048516
printf("name[4] : %#010x\n", name[4]); // 0x08048522

각각의 포인터 배열 변수는 주소 값을 저장할 수 있는 영역을 할당받기 때문에 모두 다른 주소값이 출력된다.

포인터 배열 변수와 2차원 배열을 이용하여 문자열을 할당할 때의 차이점

예를 들어, char name[5][20]은 총100byte가 할당되며 문자열을 저장하고 남더라도메모리 해제가일어나지않기 때문에 메모리 낭비가 발생할 수있다.

하지만, char*name[5]은 메모리의 낭비없이 할당되는 문자열에 맞게 메로리가 사용된다.

문자열을 다룰 때에는 배열보다는 포인터가 우수하다.

4_12.c

#include <stdio.h>

#include <string.h>

main()
{
int i, j;
char *name[5];

name[0] = "Jung jae Une";
name[1] = "Han Woo Ryong";
name[2] = "Byun Ji Ha";
name[3] = "Lee Do Geun";
name[4] = "Choi Jae Mok";

for(i=0;i<5;i++)
{
for(j=0;j<strlen(name[i]);j++)
{
while(*name[i]++ != ' ');
puts(name[i]);
break;
}
}

/*puts(name[0] + 5);
puts(name[1] + 4);
puts(name[2] + 5);
puts(name[3] + 4);
puts(name[4] + 5); */
}

gcc -o 4_12 4_12.c
./4_12
jae Une
Woo Ryong
Ji Ha
Do Geun
Jae Mok

※ 포인터 배열에 할당된 문자열에서 임의의 문자열을 추출할 경우해당 포인터 배열에 할당된 문자열의 주소위치를 이용하여 해당 값을 구할 수 있다.

포인터 배열을 사용하면서 strcpy를 사용하기 위해서는다음을 이용하자.

4_13.c

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

main()
{
char *name[5];

//char imsi[7];

name[0] = (char *)malloc(sizeof(char) * strlen("archie") + 1);

//name[0] = imsi;

strcpy(name[0], "archie");

puts(name[0]);
}

gcc -o 4_13 4_13.c
./4_13
archie

배열을 이용하여 주소를 초기화 하는 것도 가능하다.

단, 배열의 첨자를 지정할때 입력되는 문자열의길이를 미리 알고 있어야 하는 단점이 있다.

따라서, 포인터 변수는 되도록이면 mallo()을 이용하자.

'C언어' 카테고리의 다른 글

Chapter 8. 뒤죽박죽 포인터  (0) 2011.10.16
Chapter 9. 포인터의 개념을 깨는 `0`  (0) 2011.10.16
Chapter 5. 포인터의 포인터  (0) 2011.10.16
Chapter 6. scanf()와 fgets()  (0) 2011.10.16
Chapter 2. 1차원 배열과 포인터  (0) 2011.10.16