c언어 포인터와 함수와의 관계 알아보기(값에 의한 호출(call - by- value, 참조에 의한 호출(call - by - reference), swap(), scanf(), 포인터를 사용하는 반환 값)
네 안녕하세요, 이번 포스팅에서는 c언어에서 사용하는 포인터와 함수와의 관계에 대하여 알아보도록 하겠습니다.
포인터와 함수는 실제로 서로 밀접한 관계가 있습니다.
그리고 실제로 포인터의 개념을 몰라도 포인터처럼 쓰는 함수 역시 존재하고요.
그래서 그러한 함수가 무엇인지,
그리고 이 둘이 어떤 관련이 있어 관계가 깊다 그러는지 한 번 알아보도록 하겠습니다.
1. 함수를 호출했을 때의 인수를 전달하는 방식
함수 호출 시에 인수를 전달하는 방식은 크게 2가지로 나뉩니다.
- 값에 의한 호출(call -by value) : 인수의 복사본이 전달이 된다.
- 참조에 의한 호출(call -by - reference) : 인수의 원본 자체가 전달이 된다.
이렇게 두 가지로 나뉩니다.
그래서 함수를 호출했을 때, 인수가 복사가 되어서 전달이 되었다면 값에 의한 호출이 이루어진 것이고,
인수들의 원본이 전달이 되었다면 참조에 의한 호출이 됩니다.
그리고 또 한 가지는, 참조에 의한 호출을 하게 되면, 포인터를 이용하여 직접 구현이 아닌 간접 구현이 가능해집니다.
1 - 1. 값에 의한 호출
우리가 이 차이를 알아볼 수 있는 가장 간단한 방법은 swap() 함수를 이용해 보는 것입니다.
이걸 이용하는 것이 가장 전통적인 방법이기 때문에 이 방법을 채택하도록 하겠습니다.
그럼 바로 보여드리도록 하겠습니다.
#include <stdio.h>
void swap(int a1, int b1);
int main()
{
int a = 100, b = 200;
printf("a = %d, b = %d\n", a, b);
swap(a, b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
void swap(int a1, int b1)
{
int tmp;
printf("a1 = %d, b1 = %d\n", a1, b1);
tmp = a1;
a1 = b1;
b1 = tmp;
printf("a1 = %d, b1 = %d\n", a1, b1);
}
이렇게 작성을 할 수 있습니다.
그리고 결과를 먼저 말씀드리자면, 이 코드는 잘못된 코드입니다.
왜 그런 지는 결과를 먼저 보고서 말씀드리도록 하겠습니다.
a = 100, b = 200
a1 = 100, b1 = 200
a1 = 200, b1 = 100
a = 100, b = 200
결과를 보시면 알 수 있지만,
main() 함수 안에 있는 인자는 값이 바뀌지 않았고,
swap() 함수 내에 있는 인자들만 값이 바뀐 것을 확인할 수 있습니다.
이렇게 된 이유는 c언어는 기본적으로 함수 호출 옵션이 값에 의한 호출로 되어있기 때문입니다.
다시 말하면 함수의 인수로 변수 자체의 값이 복사가 되어 전달이 되기 때문에,
인수 원본의 값을 변경할 수가 없기 때문입니다.
굳이 비유하자면 복사본을 가진 사람이 원본을 가진 사람의 것을 변경할 수 없는 것과 같은 이치인 것입니다.
1 - 2. 참조에 의한 호출
그럼 반대의 경우인 참조에 의한 호출을 한 번 보도록 하겠습니다.
앞서 말했듯이, 참조에 의한 호출은 인자의 원본이 직접 전달이 된다고 했습니다.
다시 말하면 복사본이 아닌 원본이 전달되기 때문에 원본의 값을 건드릴 수 있습니다.
그리고 참조에 의한 호출을 할 때는 포인터를 이용하여 구현을 합니다.
포인터를 이용하여 목적지의 주소 값을 복사해서 전달하고, 그 주소값을 이용하여 인자의 원본값에 접근을 하여 값을 변경할 수 있게 합니다.
그럼 한 번 코드를 작성해보도록 하겠습니다.
#include <stdio.h>
void swap(int *a1, int *b1);
int main()
{
int a = 100, b = 200;
printf("a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
void swap(int *a1, int *b1)
{
int tmp;
tmp = *a1;
*a1 = *b1;
*b1 = tmp;
}
이렇게 작성을 했습니다.
값에 의한 호출에 대한 코드를 작성했을 때 보다 훨씬 간결하게 작성이 되었죠?
이게 바로 참조에 의한 호출의 장점 중 하나입니다.
그냥 주소값을 복사하여 원본 자체를 전달하기 때문에 가능합니다.
그럼 결과를 바로 보도록 하겠습니다.
a = 100, b = 200
a = 200, b = 100
이렇게 main() 함수에 있는 인자의 원본 값이 바뀌어 있는 것을 볼 수가 있습니다.
그리고 여기에서 눈여겨봐야 하는 것은 바로 swap() 함수를 호출할 때, 인자들의 앞에 주소를 가져오겠다는 표시인 '&'(엠퍼센드)를 붙였다는 것입니다.
그리고 주소 값을 복사하여 간접 참조를 하는 것이기 때문에 간접 참조 연산자인 포인터 연산자(* : 에스터 리스크)를 swap() 함수에서 사용할 인자에 붙여서 변수를 지정해야 합니다.
그래야 포인터가 제대로 된 값의 주소를 지정하게 되고, 값의 손실이나 쓰레기 값이 유입이 되는 것을 방지할 수 있습니다.
그리고 swap() 함수 내에서도 볼 수 있듯이,
포인터 변수를 선언했기 때문에 값에 의한 참조와는 다르게, 포인터 연산자가 붙은 변수가 사용이 된 것을 볼 수 있습니다.
포인터를 사용할 때 가장 많이 실수를 범하는 부분이기 때문에,
본인이 사용하고자 하는 변수의 자료형을 잘 맞춰주는 연습을 정말 많이 해야 합니다.
2. scanf() 함수
제가 포인터와 함수와의 관계에 대하여 알아보자고 그랬었죠?
그리고 그 밀접한 관련이 있는 함수가 있다고 그랬는데,
그게 바로 우리가 계속 써오고 있던 scanf() 함수입니다.
우리가 scanf() 함수를 쓸 때 항상 요구하던 게 있었습니다.
바로 주소 값입니다.
우리가 scanf() 함수를 쓸 때 '&'(엠퍼센드)를 쓰지 않으면 값을 저장할 수 없었습니다.
그 이유가 바로 c언어 함수의 기본 속성이 값에 의한 호출로 되어있기 때문이었습니다.
그래서 주소 값을 전달하는 연산자인 '&'를 붙여야만 하는 것입니다.
그래서 이를 이용하여 변수에 저장이 되어있는 주소값을 전달하여 함수의 원본에 접근이 가능하도록 하는 것입니다.
그러면 제가 이를 이용하여 함수의 기울기와 y절편을 구하는 코드를 한 번 작성을 해보도록 하겠습니다.
#include <stdio.h>
int get__line_parameter(int x1, int y1, int x2, int y2, float *slope, float *yintercept);
int main()
{
float s, y;
if (get__line_parameter(3, 3, 6, 6, &s, &y) == -1)
{
printf("에러\n");
}
else
{
printf("기울기는 %f, y절편은 %f 입니다\n", s, y);
}
return 0;
}
// 기울기와 y절편 계산
int get__line_parameter(int x1, int y1, int x2, int y2, float *slope, float *yintercept)
{
if (x1 == x2)
{
return -1;
}
else
{
*slope = (float)(y2 - y1) / (float)(x2 - x1);
*yintercept = y1 - (*slope) * x1;
return 0;
}
}
이렇게 한 번 작성을 해봤습니다.
각각은 기울기를 구하는 식과 y절편은 구하는 식을 get_line_parameter() 함수에 적용을 시켜서 만들었습니다.
그럼 결과를 한 번 보도록 하겠습니다.
기울기는 1.000000, y절편은 0.000000 입니다
결과도 문제없이 잘 나오는 것을 볼 수가 있습니다.
3. 포인터를 사용하는 반환 값
우리가 지금까지 작성했던 코드는 함수의 인수로 포인터를 받아내는 형태의 코드였습니다.
하지만 포인터는 반환 값으로도 사용이 가능합니다.
그런데 그 과정에서 한 가지 주의해야 하는 상황이 있다면,
함수가 종료가 되더라고 남아있는 메모리의 변수를 반환을 해야만 문제가 발생하지 않는다는 것입니다.
한 가지 예로, 지역 변수는 함수가 종료됨과 동시에 사라집니다.
그래서 지역 변수의 주소 값을 반환을 하게 된다면 이미 죽어있는 변수의 주소값을 반환하는 것이기 때문에 오류가 발생합니다.
예를 들어 이런 경우이죠.
int *add(int a, int b)
{
int result;
result = x + y;
return &result; // 잘못된 코드
}
이런 경우에는 실행하고 나면 지역 변수들은 각각의 함수가 종료되면서 종료가 될 것이고,
여기서 쓰인 result라는 변수 역시 그러한 상황이 되는 것입니다.
그렇지만 지금 당장은 쓰지 않을 거고,
제가 문자열 처리에 대하여 설명을 드릴 때 이야기를 드리도록 하겠습니다.
여기까지 포인터와 함수와의 관계에 대하여 알아보았습니다.
다음 포스팅에서는 포인터와 배열과의 관계에 대하여 알아보도록 하겠습니다.
간 글 읽어주신 독자분들께 진심으로 감사드립니다~
'c언어' 카테고리의 다른 글
c언어 포인터 사용의 장점에 대하여 알아보기 (0) | 2022.12.10 |
---|---|
c언어 포인터와 배열과의 관계 알아보기(배열의 이름과 포인터의 관계, 포인터를 배열처럼 사용하기, 배열 매개 변수) (0) | 2022.12.07 |
c언어 포인터로 연산하는 방법 알아보기 (0) | 2022.12.07 |
c언어 포인터 사용 시 주의할 점에 대하여 알아보기 (0) | 2022.12.07 |
c언어 간접 참조 연산자 알아보기(*, &) (0) | 2022.12.07 |
댓글