1. 함수의 인자로 배열 전달하기
인자전달의 기본방식은 값의 복사이다
노트북에 있는 영화를 큰 스크린으로 보고 싶을 때 여러분은 어떤 방법을 쓰는가?
노트북에 있는 영화를 USB로 옮겨서 볼 수도 있고 미러링으로 볼 수도 있다. USB로 영화를 복사해서 옮기는 것은 마치 인자전달의 기본방식과 같다. 즉, 함수호출 시 전달되는 인자의 값은 매개변수에 복사가 된다. 꼭 기억해야 할 점은 복사가 되는 것 뿐이기 때문에 매개변수의 값을 변경한다고 해서 인자의 값이 변경되지 않는다는 것이다. 이것이 인자전달의 기본방식이다.
#include<stdio.h>
void swap(int n1, int n2)
{
int temp = n1;
n1 = n2;
n2 = temp;
printf("n1 n2: %d %d \n", n1, n2);
}
int main(void)
{
int num1 = 10;
int num2 = 20;
printf("num1 num2: %d %d \n", num1, num2);
swap(num1, num2);
printf("num1 num2: %d %d \n", num1, num2); // "복사"가 되니까 n1과 n2의 값이 바뀌지 않음
return 0;
}
num1 num2: 10 20
n1 n2: 20 10
num1 num2: 10 20
그렇다면 복사말고 미러링을 하고 싶으면 어떻게 할까? 바로 주소의 첫번째 값을 가리켜서 메모리에 접근할 수 있는 포인터를 사용하면 된다. 이것은 마치 노트북 화면을 스크린에 미러링하면 영화를 USB에 복사하지 않고 노트북 화면을 스크린에 바로 띄워서 볼 수 있는 것과 비슷하다.
#include<stdio.h>
void swap(int *n1, int *n2)
{
int temp = *n1;
*n1 = *n2;
*n2 = temp;
printf("n1 n2: %d %d \n", *n1, *n2);
}
int main(void)
{
int num1 = 10;
int num2 = 20;
printf("num1 num2: %d %d \n", num1, num2);
swap(&num1, &num2);
printf("num1 num2: %d %d \n", num1, num2);
return 0;
}
num1 num2: 10 20
n1 n2: 20 10
num1 num2: 20 10
배열을 함수의 인자로 전달하는 방식
이제 함수의 인자로 배열을 전달하는 방법에 대해 알아보자. 매개변수로 배열을 선언하려면 어떻게 해야 할까? 매개변수만으로 배열을 통째로 선언할 수는 있는 방법이 없기 때문에 포인터로 배열의 첫번째 요소의 주소값을 가리키는 방식으로 인자를 전달할 수 있다. 포인터는 첫번째 요소의 주소 값만 가리키기 때문에, 전체 배열의 크기를 알 수가 없어서 별도로 배열의 크기를 매개변수로 전달해야 할 필요가 있다.
#include<stdio.h>
void SimpleFunc(int* ptr) {
int temp = ptr[0];
ptr[0] = ptr[1];
ptr[1] = ptr[2];
ptr[2] = temp;
}
void printArray(int* array, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", array[i]);
}
printf("\n");
}
int main(void) {
int num[] = { 1, 2, 3 };
printArray(num, 3);
SimpleFunc(num);
printArray(num, 3);
return 0;
}
2. 포인터 대상의 const 선언
포인터 변수가 참조하는 대상의 변경을 허용하지 않는 const 선언
const는 상수, 즉 변하지 않는 수이다. 포인터 변수는 인자의 첫 번째 주소값을 가리키며, 이를 통해 값을 변경할 수 있다. 하지만 보안상의 이유로 인자의 값을 변경하지 못하게 하고 싶을 때는 const를 선언할 수 있다. 선언 방법은 int 값의 변경을 허용하지 않을지, 포인터 값의 변경을 허용하지 않을지에 따라 const의 위치를 바꿔서 사용하면 된다.
포인터 변수의 상수화
1. const int * ptr: 이 형태는 "포인터를 통해 가리키는 값은 변경할 수 없다"라는 의미를 가진다. 하지만, ptr 자체는 다른 주소를 가리키도록 변경할 수 있다. 이것은 읽기 전용 데이터에 대한 포인터를 선언할 때 유용하다.
#include <stdio.h>
int main(void)
{
int num1 = 10;
int num2 = 20;
const int *ptr = &num1;
ptr = &num2; //주소 값 변경 가능
*ptr = 40; //오류 발생: 값은 변경 할 수 없음
printf("%d \n", *ptr);
}
2. int * const ptr: 이 형태는 "포인터의 주소를 변경할 수 없다"라는 의미를 가진다. 즉, ptr이 처음 가리키게 된 주소를 변경할 수 없지만, 포인터가 가리키는 값은 변경할 수 있다. 이것은 포인터가 항상 동일한 메모리 주소만을 가리켜야 할 때 사용된다. 이 때 const 선언은 포인터 변수가 가리키는 인자 자체를 상수화하는 것은 아니라는 점을 이해해야 한다. 즉, const는 포인터 변수를 통해 주소 값이 한 번 저장되면 그 주소 값만 변경할 수 없게 만드는 것이다.
#include <stdio.h>
int main(void)
{
int num1 = 10;
int num2 = 20;
int * const ptr = &num1;
/*ptr = &num2;*/ //오류 발생: 주소 값 변경 불가
*ptr = 40; // 값은 변경 할 수 있음
printf("%d \n", *ptr);
}
const 선언이 갖는 의미
결론적으로 우리는 왜 const 선언을 해야하는걸까? 프로그램 코드의 안전성을 높이기 위해서이다. const 선언을 하지 않았을 때 왜 코드의 안전성이 떨어지냐고? 휴먼 에러로 인해 사람이 값을 잘못 변경해도 컴파일러 잡아내지 못하기 때문이다.
다음 예제는 키 180cm 가 의도치 않게 160cm 이 되어버리는 슬픈 코드문이다.
#include <stdio.h>
void height(int *ptr)
{
*ptr = 160;
}
int main()
{
int myheight = 180;
printf("%d \n", myheight);
height(&myheight); // 값을 변경할 의도가 없어도 값이 변경됨, 컴파일러 못잡는다
printf("%d \n", myheight);
}
180
160
여기에서 "int myheight = 180" 을 "const int myheight = 180" 만 변경해주면 컴파일 오류가 발생하면서 의도하지 않았던 값 변경을 방지할 수 있다. 이렇듯 사소한 습관 하나로 큰 오류를 방지할 수 있다.
'윤성우의 C Programming' 카테고리의 다른 글
2차원 배열이름의 포인터 형 (0) | 2024.03.02 |
---|---|
포인터 배열과 포인터 배열의 포인터 형 (0) | 2024.03.02 |
Chapter 12. 포인터의 이해 (0) | 2024.02.28 |
Chapter 13. 포인터와 배열 (0) | 2024.02.27 |
두 개의 텍스트 파일 비교하기 (0) | 2024.02.13 |