c언어 포인터와 배열과의 관계 알아보기(배열의 이름과 포인터의 관계, 포인터를 배열처럼 사용하기, 배열 매개 변수)
네 안녕하세요, 이번 포스팅에서는 c언어에서 쓰이는 포인터와 배열과의 관계에 대하여 알아보도록 하겠습니다.
포인터와 배열은 정말 떼어내려야 뗄 수가 없을 정도로 매우 깊은 연결고리가 있어서 이 둘의 관계를 아는 것은 정말 중요한 개념을 하나 알아가는 것과 똑같습니다.
그러니 이번 포스팅은 주의깊게 보시길 바라고, 지금부터 시작하도록 하겠습니다.
1. 포인터와 배열의 관계
사실 포인터와 배열이 관계가 있다고 그러면 뭔가 이상하다 느낄 수가 있을 겁니다.
배열은 하나의 그룹을 다룰 때 쓰는 것이고, 포인터는 간접 참조를 할 때 쓴다고 배웠으니까요.
그런데, 여기서 놀라운 사실이 하나 있습니다.
그 이유는 배열의 이름이 바로 포인터이기 때문입니다.
다시 말하면, 배열의 이름은 배열이 시작되는 주소와 같습니다.
정말 신기하지 않나요?
그래서 이를 제가 증명해 보이기 위해서 간단한 코드를 하나 준비해봤습니다.
그럼 같이 보시죠.
#include <stdio.h>
int main()
{
int A[] = {10, 20, 30, 40, 50};
printf("&A[0] = %u\n", &A[0]);
printf("&A[1] = %u\n", &A[1]);
printf("&A[2] = %u\n", &A[2]);
printf("&A[3] = %u\n", &A[3]);
printf("&A[4] = %u\n", &A[4]);
printf("A = %u\n", &A);
return 0;
}
이건 그냥 간단하게 주소 값을 한 번 확인해보려고 만든 코드인데,
바로 결과를 확인해보도록 하겠습니다.
&A[0] = 6422284
&A[1] = 6422288
&A[2] = 6422292
&A[3] = 6422296
&A[4] = 6422300
A = 6422284
여기서 볼 수 있는 사실은, 아까 말씀드렸듯이, 배열의 이름이 포인터가 된다는 것입니다.
제일 마지막에 배열 A의 주 솟값을 출력한 것과, 배열 A의 0번지의 값을 출력한 결과물이 똑같은 것을 볼 수 있죠?
이래서 배열과 포인터는 아주 밀접한 관계가 있다고 하는 것입니다.
그리고 배열의 주소 값은 결과창을 보면 알 수 있듯이, 값이 연속적으로 붙어있음을 알 수 있습니다.
제가 위 코드에서 정수형으로 설정을 해두었기 때문에 정수형 자료형의 크기인 4바이트씩 띄어서 할당이 되어있음을 볼 수가 있습니다.
그러면 이제 배열의 주소 값을 출력하는 코드를 작성을 해서 배열의 이름이 포인터라는 것을 확인을 했으니,
포인터를 이용해 연산을 해봐야겠죠?
바로 보여드리도록 하겠습니다.
#include <stdio.h>
int main()
{
int A[] = {10, 20, 30, 40, 50};
printf("A = %u\n", A);
printf("A + 1 = %u\n", A + 1);
printf("*A = %u\n", *A);
printf("*(A + 1) = %u\n", *(A + 1));
return 0;
}
이렇게 한 번 작성을 해봤습니다.
이제 이러면 어떻게 되는지 한 번 결과를 봅시다.
A = 6422284
A + 1 = 6422288
*A = 10
*(A + 1) = 20
이렇게 결괏값이 나왔습니다.
보시면 알 수 있듯이, 배열 A 자체를 출력하게 되면 A가 가진 배열의 가장 첫 인덱스인 0번지의 주소 값이 나오게 되고, 거기에 1을 더해 A + 1을 만들면 정수형의 바이트 수만큼 증가해서 출력이 되는 것을 볼 수 있습니다.
그리고 간접 참조 연산자인 '*'(에스터 리스크)을 쓰면 원본의 주소 값을 복사하여 원본에 접근하는 것이기 때문에 배열 안에 있는 값이 출력됩니다.
그래서 *A는 배열의 가장 첫 번째 값인 10이 출력이 되었고,
*(A + 1)은 그 두 번째 값인 20이 출력이 되었습니다.
그리고 여기서 한 가지 중요한 사실은, 배열의 이름이 곧 포인터이긴 하지만,
배열의 이름에다가 다른 변수의 주소를 넣을 수는 없습니다.
그 이유는 배열의 이름은 다른 말로 '포인터 상수'라고 부르기 때문입니다.
그래서 어떤 방식으로든 값을 변경하는 것은 오류를 불러올 수 있으니, 주의를 하셔야 합니다.
2. 포인터를 배열처럼 사용하기
우리가 앞서 알았던 사실은 배열의 이름이 곧 포인터라고 했었죠?
그러면 반대로 포인터를 배열처럼 이용을 할 수 있을까요?
일단 결론은 '사용할 수 있다'입니다.
배열의 이름이 포인터이기 때문에, 포인터가 배열의 이름으로 간주가 될 수 있고,
배열과 똑같이 사용을 할 수가 있습니다.
그러면 이를 이용한 간단한 예제를 보여드리도록 하겠습니다.
#include <stdio.h>
int main()
{
int A[] = {10, 20, 30, 40, 50};
int *p;
p = A;
printf("A[0] = %d, A[1] = %d, A[2] = %d\n", A[0], A[1], A[2]);
printf("p[0] = %d, p[1] = %d, p[2] = %d\n\n", p[0], p[1], p[2]);
p[0] = 60;
p[1] = 70;
p[2] = 80;
printf("A[0] = %d, A[1] = %d, A[2] = %d\n", A[0], A[1], A[2]);
printf("p[0] = %d, p[1] = %d, p[2] = %d\n\n", p[0], p[1], p[2]);
return 0;
}
이렇게 한 번 제가 써봤습니다.
그럼 결과는 어떻게 나올까요?
제가 앞서 말씀드렸던 것처럼, 포인터와 배열의 이름이 같은 기능을 한다고 했으니,
둘 다 같은 값이 나와야 이게 성립하겠죠?
그럼 바로 결과를 봅시다.
A[0] = 10, A[1] = 20, A[2] = 30
p[0] = 10, p[1] = 20, p[2] = 30
A[0] = 60, A[1] = 70, A[2] = 80
p[0] = 60, p[1] = 70, p[2] = 80
p의 배열을 바꾸기 전과 후의 결과입니다.
이렇게 배열의 이름과 포인터가 가리키는 값이 똑같이 나온 것을 볼 수가 있습니다.
3. 배열 매개 변수
예전에 작성했던 포스팅들에서 배열을 다뤘었는데, 그때 제가 일반 매개 변수와 배열 매개 변수는 조금 차이가 있다고 그랬습니다.
일반 매개 변수와 배열 매개 변수는 이러한 차이를 가지고 있습니다.
- 일반 매개 변수 : 매개 변수로 선언된 x에는 실제 메모리가 할당이 되므로 지역 변수와 동일한 기능을 함.
- 배열 매개 변수 : 매개 변수로 선언된 배열 B에는 배열이 저장되지 않고, 외부에서 전달되는 배열의 주소를 저장하는 포인터로 생성을 함.
이러한 차이가 있습니다.
그래서 실제 값을 바로 입력을 하느냐, 포인터를 생성하여 주소 값을 이용하여 생성하느냐의 차이입니다.
그래서 함수 호출 시에 배열을 전달하게 되면, 배열의 값이 아닌 배열의 주솟값을 받게 됩니다.
제가 배열의 이름이 곧 포인터라서 포인터를 배열처럼 쓸 수 있다 그랬었죠?
그게 바로 이러한 경우에 쓰이는 것입니다.
그러면 이러한 것을 한 번 간단한 예제를 통하여 같이 살펴보도록 하겠습니다.
#include <stdio.h>
void sub(int B[], int size);
int main()
{
int A[3] = {1, 2, 3};
printf("%d %d %d\n", A[0], A[1], A[2]);
sub(A, 3);
printf("%d %d %d\n", A[0], A[1], A[2]);
return 0;
}
void sub(int B[], int size)
{
B[0] = 4;
B[1] = 5;
B[2] = 6;
}
이렇게 작성을 해봤습니다.
main() 함수 내에 있는 printf() 함수의 출력 결과는 sub() 함수의 작용 전과 후의 결괏값이 출력이 될 것입니다.
그럼 결과를 한 번 보도록 하겠습니다.
1 2 3
4 5 6
결과도 잘 나온 것이 확인이 되었습니다.
포인터는 어떤 변수의 주소 값을 복사해 해당 주소 값으로 접근해서 원본 값을 적용시키기 때문에 값의 원본 값이 출력이 된 것을 볼 수가 있습니다.
그리고 sub() 함수의 매개 변수를 이런 식으로 표현할 수 있습니다.
void sub(int *B, int size)
포인터와 변수의 이름은 같은 기능을 하기 때문에 이렇게 써도 문제가 없습니다.
그러니 본인의 취향에 따라서 쓰시면 됩니다.
그럼 이쯤에서 한 가지 의문이 들지 않으시나요?
왜 배열의 매개 변수는 실제 값 대신에 포인터를 이용하여 주소 값을 받아오는지 말이죠.
일반적인 변수들은 그냥 값 자체를 받아와도 큰 문제가 없지만, 배열은 그렇게 했을 때 문제가 생길 수도 있습니다.
그 이유는 제가 이전 포스팅에서도 말씀드렸듯이, c언어는 기본적으로 값에 의한 참조를 차용하고 있다고 그랬었습니다.
하지만, 이 경우는 일대일 대응이 가능한 일반적인 변수들만 가능한 이야기이고,
하나의 그룹을 지어서 나타내는 배열의 경우는 배열 속 값이 하나일 수도 있지만, 기본적으로 여러 개를 가지고 있죠?
그리고 그 배열의 수가 천문학적으로 클 수도 있습니다.
그래서 이 값 자체를 복사한다고 해서 직접적인 문제가 있다는 것은 아니지만, 상당한 시간을 소모할 수 있기 때문에 매우 비효율적인 작업입니다.
그래서 원본 배열을 놔두고서 주소 값을 복사하여 포인터를 통해 원본 배열로 접근을 하게끔 만들어둔 것입니다.
예를 들자면 우리가 물건을 주고받을 때 택배사를 쓰는 이유가 무엇일까요?
직접 전달하기에는 시간이 너무 많이 들어서죠?
그래서 택배사를 통해서 물건을 받게 됩니다.
다시 말하면, 물건을 가진 사람이 물건을 전달을 할 때, 택배사라는 포인터를 이용하여 원본 배열인 물건을 받을 사람에게 접근을 하는 것이죠.
이러는 방법이 훨씬 일률적으로 좋기 때문에 이런 방법을 권장합니다.
여기까지 포인터와 배열과의 관계에 대하여 알아보았는데요,
다음 포스팅에서는 포인터를 사용하는 것에 대한 장점에 대하여 알아보도록 하겠습니다.
긴 글 읽어주신 독자분들께 진심으로 감사드립니다~
'c언어' 카테고리의 다른 글
c언어 포인터 이용하여 여러가지 문제 풀어보기(영상 처리 하기, 자율 주행 시뮬레이션 구현하기) (0) | 2022.12.13 |
---|---|
c언어 포인터 사용의 장점에 대하여 알아보기 (0) | 2022.12.10 |
c언어 포인터와 함수와의 관계 알아보기(값에 의한 호출(call - by- value, 참조에 의한 호출(call - by - reference), swap(), scanf(), 포인터를 사용하는 반환 값) (0) | 2022.12.07 |
c언어 포인터로 연산하는 방법 알아보기 (0) | 2022.12.07 |
c언어 포인터 사용 시 주의할 점에 대하여 알아보기 (0) | 2022.12.07 |
댓글