본문 바로가기

C언어

Chapter 12. 될 것 같으면서 안 되는 코드

Chapter 12.될 것 같으면서 안 되는 코드

초보자들이 아무리 들여다봐도 결국에는 해결하지 못하고나중에는 편법을 자행하는 대표적인것으로쓰레기 주소 값이다.

char *imsip;

strcpy(imsip, "archie");

현재 imsip가 가리키는 번지는 쓰레기 값이므로 문제가 발생한다.

이를 해결하려면malloc()을 사용하는 것인데 사용할 때마다 추가해 주어야 하는 번거로움이 있다.

이를대신 해주는 함수를 이용한 프로그램을 작성해 보자. 더불어 하나의 문자열 뿐만 아니라 여러개의 문자열도 처리할 수 있도록 해보자.

12_1.c

#include <stdio.h>
#include <stdlib.h> // malloc(), NULL
#include <stdarg.h> // va_start(), va_arg(), va_end()
#include <string.h> // strlen()

int my_strcpy(char *imsip, ...)
{
va_list list;
char *temp;
int total_string_size = 0;

va_start(list, imsip);

while((temp = va_arg(list, char *)) != NULL)
total_string_size += strlen(temp);

va_end(list);

imsip = (char *)malloc(sizeof(char) * total_string_size + 1);
if(imsip == NULL) return 0;

va_start(list, imsip);

while((temp = va_arg(list, char *)) != NULL)
strcat(imsip, temp);
va_end(list);
}

main()
{
char *imsip;
int return_value = 0;

return_value = my_strcpy(imsip, "archie", " is ", "GOOD", (char *)0);

if(return_value == 0) puts("malloc error"), exit(0);

puts(imsip);
}

gcc -o12_1 12_1.c

./12_1

?멸렇硫???댁? ?ㅻ?

컴파일 에러는 없지만 결과는 원하던 결과가 아니다.

위의 경우는 가변 인자를 사용한 경우이니 좀더 간결한프로그램으로 에러의 원인을 잡아보자.

12_2.c

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

main()
{
void reverse(char *, char *);
char *imsi;

reverse(imsi, "archie");
printf("reverse imsi : %s\n", imsi);
}

void reverse(char *save, char *source)
{
int temp, i, j;

save = (char *)malloc(sizeof(char) * strlen(source) + 1);

strcpy(save, source);

for(i=0,j=strlen(source)-1;i<j;i++,j--)
{
temp = save[i];
save[i] = save[j];
save[j] = temp;
}

printf("source : %s\n", source);
printf("save : %s\n", save);
}

gcc -o12_2 12_2.c

./12_2

source : archie
save : eihcra
reverse imsi : U?WVS?t

reverse()는 두 개의 인자를 받아 들이는함수이며 이 두 개의 인자는 모두 주소 값이다.

imsi에 저장된 주소 값과"archie" 문자열 중에서'a'가 저장된 곳의 주소 값이다.

reverse()함수가 호출되면 imsi에 저장된 주소 값이 save로 넘겨진다.

save는 지역 포인터 변수이며 주소값을 저장 할 수 있는 변수다. source도 지역 포인터 변수이며 주소 값을 받는다.

여기서save에 저장되는 주소 값은 imsi가 가지고 있는 쓰레기 주소 값이 된다. 이를 초기화 해주기 위해서 아래와같이malloc()을 이용한다.

save =(char*)malloc(sizeof(char) *strlen(source) + 1);

이로써save는 imsi가 가리키고 있던 값을 버리고 새로운 영역을 가리키는값으로 대체 되었다.

따라서, reverse() 함수를 수행하는 동안 "archie"의 뒤집힌 문자열인 "eihcra"가 저장될 것이다.

하지만, save와source라는 변수는 지역 변수이기 때문에 reverse() 함수가 수행을 마치는 순간메모리에서 사라지기때문에존재하지 않는다.

reverse() 함수가 수행을 마친 후 수행되는 코드는 printf("reverse imsi : %s\n", imsi) 이며 printf는 imsi가 가리키는 영역을 출력하려고 할 것이다.

imsi는쓰레기 주소 값을 갖고 있으며NULL 문자를 만날때까지 이상한 문자들이 출력되는 것이다.

왜 이 프로그램이 잘 수행될 것이라고 착각하게되는 걸까?

값에 의한 호출(call by value)와 참조에 의한 호출(call by reference)의 원리를 정확히 이해하고 있지 않기 때문일 것이다.

12_3.c

#include <stdio.h>

main()
{
void swap(int, int); // call by value
int a, b;

a = 10;
b = 7;

swap(a, b);
printf("a = %d, b = %d\n", a, b);
}

void swap(int imsi_a, int imsi_b)
{
int temp;

temp = imsi_a;
imsi_a = imsi_b;
imsi_b = imsi_a;
}

gcc -o 12_3 12_3.c

./12_3

a = 10, b = 7

a, b에 저장된 정수를 바꾸기 위해swap()함수를 사용하였지만,

swap() 함수에서 사용한 변수imsi_a, imsi_b는 main()에 있는a, b와는 전혀 별개의 변수이다.

지역 변수인 imsi_a, imsi_b는 swap()함수의 수행이 끝나면 메모리에서 사라지게 되고 main()의 a, b는 변화가 없다.

이를 해결하기 위해서 다음과 같이 주소를 넘겨 처리한다.

12_4.c

#include <stdio.h>

main()
{
void swap(int *, int *);
int a, b;

a = 10;
b = 7;

swap(&a, &b);
printf("a = %d, b = %d\n", a, b);
}

void swap(int *imsi_a, int *imsi_b)
{
int temp;

temp = *imsi_a;
*imsi_a = *imsi_b;
*imsi_b = temp;
}

gcc -o 12_4 12_4.c

./12_4

a = 7, b = 10

주소 값을 swap() 함수에 전달했기 때문에 두 변수의 값이 바뀌어 있다.

바로 이 원리로 인해 reverse() 함수를사용한 프로그램도 같은 맥락으로 변화 시킬 수 있다고 생각한 것이다.

하지만, malloc()을 사용하는 순간main()에서의 변수와 호출 함수의 지역 변수는 완전히 다른주소 값을 갖게 되고 원하지 않던 값을 얻게 된 것이다.

위의 문제점을 해결하기 위해서는main() 함수에서 reverse() 함수를 호출하기 전에

포인터 변수 imsi를 malloc()을 이용해 초기화를 해주거나 imsi를 배열로 선언하여 전달하면 된다.

reverse() 함수에서 save를 초기화하는 malloc() 부분을 삭제한다.

12_5.c

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

main()
{
void reverse(char *, char *);
//char imsi[7];
char *imsi;
imsi = (char *)malloc(sizeof(char) * 7 + 1);

reverse(imsi, "archie");
printf("reverse imsi : %s\n", imsi);
}

void reverse(char *save, char *source)
{
int temp, i, j;

//save = (char *)malloc(sizeof(char) * strlen(source) + 1);

strcpy(save, source);
for(i=0, j=strlen(source)-1;i<j;i++, j--)
{
temp = save[i];
save[i] = save[j];
save[j] = save[i];
}

printf("source : %s\n", source);
printf("save : %s\n", save);
}

gcc -o12_5 12_5.c

./12_5

source : archie
save : eihhie
reverse imsi : eihhie

reverse()에 넘길 때의 imsi 주소 값은 메모리 할당된 주소 값이다.

메모리에 7byte가 할당 되었으며 이 할당된 주소 값이save에 전달되는 것이다.

전달 주소를 reverse()에서 이용하므로 당연히 원본이 바뀌게된다.

함수를 호출할 때 쓰레기 주소 값을 전달하지 않고 할당된 메모리 주소 값만 넘기면 해결되는 문제다.

따라서, 맨 처음의 12_1의 경우를 수정하면 아래와 같다.

12_1.c 수정

#include <stdio.h>
#include <stdlib.h> // malloc(), NULL
#include <stdarg.h> // va_start(), va_arg(), va_end()
#include <string.h> // strlen()

void my_strcpy(char *imsip, ...)
{
va_list list;
char *temp;
int total_string_size = 0;

va_start(list, imsip);
while((temp = va_arg(list, char *)) != NULL)
total_string_size += strlen(temp);
va_end(list);

va_start(list, imsip);
while((temp = va_arg(list, char *)) != NULL)
strcat(imsip, temp);
va_end(list);
}

main()
{
char *imsip = (char *)malloc(15);

my_strcpy(imsip, "archie", " is ", "GOOD", (char *)0);
puts(imsip);
}

gcc -o 12_1 12_1.c
./12_1
archie is GOOD

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

Chapter 10. 바로알자 getchar()  (0) 2011.10.16
Chapter 11. 가변 인자  (0) 2011.10.16
Chapter 7. strcpy()의 비밀  (0) 2011.10.16
Chapter 8. 뒤죽박죽 포인터  (0) 2011.10.16
Chapter 9. 포인터의 개념을 깨는 `0`  (0) 2011.10.16