본문 바로가기
C C++/C언어 기초

C언어 - 포인터와 함수 관계의 모든 것

by Go! Jake 2022. 5. 3.

함수는 인자를 가질 수 있고, 반환 값이 있도록 정의할 수 있습니다. 지금까지는 포인터를 인자로 가지는 경우를 다룬 적이 없고, 포인터를 반환 값으로 하는 함수를 다룬 적은 없습니다. 이번에는 포인터와 함수 관계를 알아보도록 하겠습니다.

함수의 인자 전달하기

함수의 인자는 함수 내 매개변수에 값을 전달하는 역할을 합니다. 여기서 중요한 부분은 '매개변수'에 전달한다는 점입니다. 함수 안의 매개 변수는 이 값을 전달 받고, 함수 내에서 연산이 되기도 합니다.

 

#include <stdio.h>

void showinfo(int b)
{
	b+=1;
	printf("%d\n", b);
}

int main(){
	int a = 1;
	showinfo(a);
	printf("%d", a);
	return 0;
}

출력:

2

1

 

예를 들어 함수의 a라는 인자는 값을 전달하는 역할만 하고, 함수 내의 연산을 통해 실제 a 값이 변하지는 않습니다.

- void showinfo(int b) 함수 내 b+=1;를 통해 전달받은 인자 b에 값을 증가시켰습니다.

- main 함수에서는 a 값을 1로 초기화하였고, showinfo(a)를 통해 a를 인자로 전달하였습니다.

- showinfo 함수 내에서 출력한 b 값은 1만큼 증가하여 2로 증가하였고, main 함수에서 다시 a를 출력하자 1이 나왔습니다.

 

즉, 값만 복사해서 전달하는 것입니다.

배열이 함수의 인자가 되는 경우 (포인터)

배열을 인자로 하여, 함수 내 매개변수가 배열을 전달 받을 수는 없습니다. 따라서 이 경우엔 주소 값, 즉 포인터를 통해 배열 인자를 전달 받습니다. 예제부터 보도록 하겠습니다.

 

#include <stdio.h>


void show_array(int* arr)
{
	printf("%d %d %d", arr[0], arr[1], arr[2]);
}

int main(){
	int numbers[3] = {1,2,3};
	show_array(numbers);
	return 0;
}

출력:

1 2 3

 

- 함수에 인자로 배열을 받을 때, 포인터를 선언하여 받습니다. showarray(int* arr)로 선언하였습니다.

- int numbers[3]에서 배열 이름인 numbers가 주소 값이므로 그대로 함수에 입력합니다. 그리고 인덱스 번호를 추가하여 각 배열을 참조할 수 있습니다.

- show_array 내 printf 함수를 통해 arr[0], arr[1], arr[2] 값이 표출되었습니다.

 

이번에는 배열을 인자로 받아 값을 연산하는 함수를 작성하도록 해 보겠습니다.

#include <stdio.h>


void Addarray(int* arr, int a)
{
	int i;
	for (i=0; i<3; i++)
	{
		arr[i]+=a;
	}
}

int main(){
	int a = 3;
	int numbers[3] = {1,2,3};
	printf("Before add: %d %d %d\n", numbers[0], numbers[1], numbers[2]);
	Addarray(numbers,a);
	printf("After add: %d %d %d", numbers[0], numbers[1], numbers[2]);
	
	return 0;
}

출력:

Before add: 1 2 3
After add: 4 5 6

 

- Addarray(int* arr, int a)로 선언하여, int형 배열 주소와 a 값을 인자로 받았습니다.

- 전달받은 인자로 arr[i]+=a; 즉 각 배열 요소에 a를 더하는 연산을 수행하였습니다.

- 전후 결과로 int a = 3; 만큼 증가된 것을 볼 수 있습니다.

 

현재까지 void Function (int* param, int len) 등의 함수 선언을 사용하였습니다. 배열로 인자를 받을 때 한 가지 더 알아야될 부분이 있습니다. 바로 아래와 같이 선언할 수 있다는 점입니다.

 

- void ShowArrayElem(int param[], int len) ...

실제로 int param[]과 int* param은 동일한 선언입니다. 다만 배열 인자를 받는다는 점을 강조하기 위해 가독성을 위해 int param[]으로 사용하는 경우도 많습니다.

Call-by-value vs. Call-by-reference

실제로 C언어에는 Call-by-reference의 호출 방식은 없다고 합니다. 다만 이와 유사하게 흉내를 내고 있습니다. C언어에는 없지만 아래와 같이 두 개념을 정리하도록 하겠습니다.

 

- 함수 호출 시 '값'을 전달하는 함수 호출을 Call-by-value라고 합니다. 매개 변수에 값만 복사합니다.

- 함수 호출 시 '주소 값'을 전달하는 함수 호출을 Call-by-reference라고 합니다. 주소 값을 전달하여 접근합니다.

 

값 전달: Call-by-value

위에서 여러 함수를 다루면서 인자 값이 함수 내 매개변수로 전달되었지만, 실제 인자 값이 함수 밖에서는 바뀌지 않은 경우를 여러 차례 보았습니다. 이전에 보았던 아래 예시입니다.

#include <stdio.h>

void showinfo(int b)
{
	b+=1;
	printf("%d\n", b);
}

int main(){
	int a = 1;
	showinfo(a);
	printf("%d", a);
	return 0;
}

출력:

2

1

 

즉, 함수 밖에서는 int a 값이 전혀 바뀌지 않았습니다. 값만 함수 안에 복사 해 준 것 뿐, 실제 값이 변경되지 않은 것입니다.

 

그렇다면 실제 값도 바꾸지 않는 call-by-value를 왜 사용할까요? 실제 값에 영향이 있어야 의미 있는 게 아닐까요?

call-by-value에 대한 사용은, 예를 들어 하나의 변수를 사용하는 여러 함수가 있는 경우 이에 사용되는 인자 값은 함수에 인자로 쓰일 때마다 변경되어 관리가 어려운 점이 있습니다. 따라서 매개 변수를 이용하여 값을 받고, 필요하면 이를 return하여 사용하는 방식이 사용되기도 합니다.

주소 값 전달: Call-by-reference

주소 값을 전달하고 그 주소 값에 접근하여 근본적으로 주소 값이 가진 값을 변경할 수 있습니다.

주소 값을 전달하여 호출하는 방식을 Call-by-reference라고 합니다.

 

Call-by-value에서는 인자는 매개 변수에만 복사만 해 줄 뿐, 인자 값이 함수 연산을 통해 실제로 변경되지 않았습니다.

Call-by-reference는 주소 값을 전달하여, 실제 그 주소 내 값에 접근하기 때문에 값 변경이 가능합니다.

 

아래 예시를 살펴 보겠습니다.

#include <stdio.h>


void Addarray(int* a)
{
	*a += 1;
}

int main(){
	int b = 3;
	printf("Before add: %d\n", b);
	Addarray(&b);
	printf("After add: %d", b);
	
	return 0;
}

출력:

Before add: 3
After add: 4

 

int b 값이 3에서 4로 변경된 것을 알 수 있습니다.

-  void Addarray(int* a) 함수 선언을 통해 인자로, 주소 값을 전달 받습니다.

-  Addarray(&a)를 통해 a의 주소 값을 인자로 전달.

- *a를 통해 a 주소값 내 값에 접근합니다. 1을 더합니다.

- b를 출력 해 보면 3에서 4로 증가한 것을 알 수 있습니다.

즉 b의 주소 값에 접근하여 실제 b 값을 변경한 것입니다. Call-by-reference는 이와 같이 사용될 수 있습니다.

해당 내용은 윤성우 열혈 C프로그래밍을 참조하였습니다.

댓글