간단하게 a와 b를 서로 바꿔주는 swap 함수를 만들어주세요. main 함수는 주어집니다.
int main()
{
const char* a = "abcd";
const char* b = "12345";
printf(a); //"abcd"
printf(b); //"12345"
str_swap();
printf(a); //"12345"
printf(b); //"abcd"
}
str_swap()에 들어갈 매개변수는 자유롭게 설정하시길 바랍니다.
아마 가장 유명한 문제가 아닐까 싶습니다. 이 문제를 푸는 방법을 설명드리겠습니다.
우선 다음의 문자열 선언에 대해 이해할 필요가 있습니다.
const char* a = "abcd";
abcd 라는 문자열 리터럴을 가리키는 포인터 변수 a입니다. 보통 우리가 string형을 선언할 때 크기를 지정해주어야 했습니다. 그러나 위와 같은 방식으로 선언하면 크기를 지정해주지 않아도 됩니다.
그러나 리터럴이라면 앞에 const를 붙여 상수로 취급을 해주기를 권장합니다. 리터럴은 const를 붙이기 때문에 수정할 필요가 없는 경우에 사용하는 것이 좋습니다.
그 다음에.. cosnt가 앞에 붙었는데, 수정이 불가능하다는 것은 무엇이고? 이 범위는 어디까지인가요?
잠시 포인터에 대한 개념을 복습하고 갑시다.
const char* a = "abcd";
앞서 본대로 이렇게 선언을 하는 방법이 있고,
#define STR_LENGTH 10000
int main
{
char arr[STR_LENGTH] = "abcd";
const char * p = &arr
}
위의 방법으로 선언하는 방법이 있습니다. 위 둘은 어떤 차이가 있는 것일까요?
메모리의 크기를 정의했다는 차이를 제외하고 확인해보겠습니다. 리터럴을 사용하면 문자열 데이터를 상수로 취급하며 수정할 수 없습니다.
배열로 할당을 받으면 배열자체는 수정 가능하고, 문자열 데이터에 대한 조작이 가능합니다. 포인터를 사용한 변경을 불가능
일단 코드적으로 실험을 조금 해봅시다.
const char* a = "bcda";
char arr[STR_LENGTH] = "abcd";
const char* p = &arr;
arr[0] = '1';
printf("%s \n", a);
printf("%s \n", p);
return 0;
이 결과는 잘됩니다. 그러나
위의 경우는 수정이 불가능합니다.
근데, 조금 웃긴게 있습니다. 상수로 선언된 포인터는 가리키는 대상은 변경이 가능합니다.
const char* a = "bcda";
char arr[STR_LENGTH] = "abcd";
const char* p = &arr;
const char* c = "";
c = a;
a = p;
p = c;
printf("%s \n", a);
printf("%s \n", p);
return 0;
다음처럼 말이죠.. 원래라면 a는 bcda 여야 하고, p는 abcd여야 합니다. 그러나 가리키는 대상을 변경하는 것은 가능합니다.
즉 const 로 선언된 포인터는..
가리키는 대상을 바꿀 수는 있지만, 포인터로 내부를 수정할 수 없습니다.
리터럴로 선언된 경우 배열(문자열)을 직접 조작하는 것이 불가능합니다. 그래서 문자열을 지킬 수 있습니다.
그러나 배열로 선언하여 상수포인터로 가리키는 경우 상수포인터로는 수정이 불가능하나 배열에 직접 접근하여 수정이 가능함을 보았습니다.
그러면 다시 문제로 돌아와서
이 상태입니다. a와 b를 변경하는 함수를 만들고 싶습니다. 하나만 더 보고 가야합니다. 함수에 매개변수를 전달하는 방법을 한 번 확인해봅시다.
만약에 이 상태라면 매개변수로 어떤 값을 주어야할까요?
이 경우일 수 있습니다. 이 경우는 매개변수로 배열 자체를 넘기는 것이라고 생각할 수 있습니다.
다음의 결과는 어떻게 될까요? 우리는 배열을 넘겼고, 배열의 0번째 원소를 수정했어..
결과는?
main에 있는 친구가 수정됩니다.
왜 이런 경우가 발생할까요?
우리가 평소에 알고있는 상식?이라면
a와 b의 매개변수로는 복사본이 전달됩니다. 그래서 결론적으로 a와 b값은 main에 영향을 주지 않아야 정상입니다.
근데, 배열의 경우는 다릅니다.
void int_swap(int a[], int b[])
{
a[0] = 0;
b[0] = 1;
}
int main()
{
int a[2] = { 5, 0 };
int b[2] = { 7, 1 };
int_swap(a, b);
printf("%d, %d", a[0], b[0]);
}
값이 바뀝니다..
?? 도대체 무슨 원인일까요?
우선 a와 b를 출력해봅시다.
두 배열의 주소값은 서로 같습니다.... 이 값이 무엇을 의미하는가?
void int_swap(int a[], int b[])
{
printf("%d, %d \n", a, b);
printf("%d, %d \n", &a[0], &b[0]);
a[0] = 0;
b[0] = 1;
}
int main()
{
int a[2] = { 5, 0 };
int b[2] = { 7, 1 };
int_swap(a, b);
printf("%d, %d \n", a, b);
printf("%d, %d \n", &a[0], &b[0]);
바로 배열의 첫번째 원소를 가리킵니다.
C언어에서는 배열을 매개변수로 보낼때 첫 번째 원소의 주소값을 보내게 되어있습니다. 그래서 int_swap 함수에서 a배열을 수정하게 되면... main에 있는 a배열을 건들게 됩니다.
보통 이런 실수들이 상당히 자주 발생하다 보니 const란 개념이 등장했다고 말합니다.
다음처럼 매개변수에 const를 붙여주게 되면 a배열과 b배열의 값이 수정되는 것을 막을 수 있습니다.
그러나..
main에서 const로 배열을 선언해봤자.. 다른 함수에서 변경하는 것을 막을 수 없습니다.
그래서 문제를 살짝만 바꿔봅시다.
이 경우에 swap하는 코드를 짜봅시다.
앞서 살펴본 것처럼
void str_swap_array(char a[], char b[] ,size_t a_length, size_t b_length)
{
int str_length;
if (a_length > b_length)
str_length = a_length;
else
str_length = b_length;
char* temp = (char*)malloc(sizeof(char) * (str_length + 1));
strcpy(temp, a);
strcpy(a, b);
strcpy(b, temp);
}
int main()
{
char a[STR_LENGTH] = "abcd";
char b[STR_LENGTH] = "12345";
printf(a); //"abcd"
printf("%s\n", b); //"12345"
str_swap_array(a, b, strlen(a), strlen(b));
printf(a); //"12345"
printf(b); //"abcd"
}
다음처럼 코드를 짜면 정말 손쉽게 swap이 가능합니다.
근데, 우리의 문제는 배열이 아닙니다.
리터럴입니다.
아까 풀이했던대로 그대로 적용하면
void str_swap(char a[], size_t a_length, char b[], size_t b_length)
{
int str_length;
if (a_length > b_length)
str_length = a_length;
else
str_length = b_length;
char* temp = (char*)malloc(sizeof(char) * (str_length+1));
strcpy(temp, a);
strcpy(a, b);
strcpy(b, temp);
}
int main()
{
const char* a = "abcd";
const char* b = "12345";
printf(a); //"abcd"
printf(b); //"12345"
str_swap(a, strlen(a), b, strlen(b));
printf(a); //"12345"
printf(b); //"abcd"
}
뭔가 어렵지만, 메모리 엑세스 위반이 발생합니다. 그 이유가 현재 a변수를 수정하려고 합니다.
문자열 a는 현재 리터럴이며, 수정되면 안되기 때문이라고 합니다. 그래서 strcpy로 a값을 b로 수정 하려고 하는 경우 예외가 발생하게 됩니다.
그러면 어떻게 접근을 해야할까요?
포인터를 사용해서 접근을 해야합니다. 우선 a가 가리키는 대상은 변경이 가능하다고 했습니다.
그러면 포인터를 통해서 접근을 해봅시다.
void str_swap(char *a, char* b)
{
char* temp = "";
temp = a;
a = b;
b = temp;
}
int main()
{
const char* a = "abcd";
const char* b = "12345";
printf(a); //"abcd"
printf("%s\n", b); //"12345"
str_swap(a, b);
printf(a); //"12345"
printf(b); //"abcd"
}
자 이제.. 맞는 결과가 나올까요?
결과는 전혀 swap이 일어나지 않고 있습니다.
위에서 살펴본 내용 중에서 매개변수를 넘길때
배열이라면 첫 번째 값의 주소가 넘어가고, 단독?변수라면 복사된 값이 넘어간다고 했습니다.
그러니까
여기서 a와 b는 str_swap에서 사용되는 지역변수입니다.
이제 거의다왔습니다. 이 친구를 수정하려면 어떻게 해야할까요?
포인터의 포인터를 넘겨주면, str_swap에서 포인터가 가리키는 값을 수정하면 될 것입니다.
우리가 포인터를 선언할때 어떤식으로 선언을 했을까요
char arr[] = "1234";
char * p = &arr;
포인터 변수 p는 arr의 첫번째 변수의 주소를 가리켜야합니다.
그러면 char *a가 지역변수라고 했을때, 이 친구의 포인터는 무엇일까요?
*(char *a) = &a
이겠죠? 그러니까 즉 매개변수로는 이중포인터가 됩니다.
void str_swap(char **a, char **b) //변경점
{
char* temp = "";
temp = *a;
*a = *b;
*b = temp;
}
int main()
{
const char* a = "abcd";
const char* b = "12345";
printf(a); //"abcd"
printf("%s\n", b); //"12345"
str_swap(&a, &b); // 변경점
printf(a); //"12345"
printf(b); //"abcd"
}
이런식으로 코드가 만들어진다면 성공적으로 swap이 가능합니다.
const char* a 의 모양을 있는 그대로 받아들이는 문제? 같네요. 뭔가 어려우면서 재미있는 문제였습니다.