본문 바로가기

매개변수

1.  CBV VS CBR

 

함수 호출 방식에는 CBV(Call-By-Value)와 CBR(Call-By-Reference)의 두 가지가 있다.

전자는 값에 의한 호출, 즉 값을 함수로 전달하는 방식이고 후자는 참조에 의한 호출, 즉 메모리 접근을 위한 주소값을 전달하는 방식이다. 따라서 앞에서 정의한 함수들은 모두 CBV 방식이라고 할 수 있다.

 

그런데 왜 이 둘을 구분하는 걸까?

다음과 같이 두 수의 값을 바꾸는 swap 함수를 정의한다고 해보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
 
void swap(int a, int b);
 
int main()
{
    int num1= 3, num2 = 5;
 
    printf("num1: %d, num2: %d\n", num1, num2);
    swap(num1, num2);
    printf("num1: %d, num2: %d\n", num1, num2);
 
    return 0;
}
 
void swap(int a, int b)
{
    int temp;
 
    temp = a;
    a = b;
    b = temp;
 
    printf("a: %d, b: %d\n", a, b);
}
cs

 

 

 num1 과  num2 의 값이 서로 바뀌길 기대했지만 바뀌지 않았다.

왜 그럴까?

 

 swap  함수 내에서는 실제로 다음과 같은 과정에 의해 값이 바뀐다.

 

 

 

초기에는  temp 는 ⒜비어 있고  a 에는  3 b 에는  5 가 들어 있다.

그리고  temp = a; 에 의해  temp 에  a  값이 저장되고  a = b 에 의해  b  값이  a 에 저장된다. 그리고  b = temp 에 의해  b 에  temp 가 저장되면 미리 기억해둔  a  값이 저장되어  a

 b 의 값이 서로 바뀌는 것이다.

 

그런데 여기서  num1 과  num2 의 값이 바뀌지 않은 것은 

함수 호출 시 매개변수에 값이 복사되어 전달되기 때문이다. 

다시 말해, 인자 전달의 기본은 값의 복사다.

그러니까 여기서는  num1 과  num2  자체가 전달된 게 아니라, 

 num1 의 값인  3 과  num2 의 값인  5 가 

매개변수  a 와  b 에 복사되어  swap  함수로 전달된 것이다.

 

따라서  num1 num2 와  a b 는 완전히 별개다.

 

 

 

이런 방식을 CBV(Call-By-Value)라 한다.

 

⒜비어 있고: 엄밀히 말하면 쓰레기값이 들어 있다.

 

 

그렇다면 어떻게 해야 함수로 두 변수의 값을 바꿀 수 있을까?

앞에서 정의한 것과 같이 값을 전달하는 게 아니라 주소를 전달하면 된다.

주소를 전달해 그 주소에 있는 값을 바꾸는 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
 
void swap(int* a, int* b);
 
int main()
{
    int num1= 3, num2 = 5;
 
    printf("num1: %d, num2: %d\n", num1, num2);
    swap(&num1, &num2);
    printf("num1: %d, num2: %d\n", num1, num2);
 
    return 0;
}
 
void swap(int* a, int* b)
{
    int temp;
 
    temp = *a;
    *= *b;
    *= temp;
 
    printf("a: %d, b: %d\n"*a, *b);
}
cs

 

 

여기서 매개변수  a 에는  num1 의 주소가,  b 에는  num2 의 주소가 전달되므로

 *a 는  num1 이 되고  *b 는  num2 가 된다.  

따라서 이 경우  num1 과  num2 의 값이 바뀌게 되는 것이다.

이런 방식을 CBR(Call-By-Refernce)라 한다.

 

scanf 함수 호출 시 다음과 같이 변수의 주소값을 전달하는 것도 CBR의 예다.

 

scanf("%d", &num);

 

만약  &num 이 아니라  num 을 전달했다면  num 에 담긴 값이 복사되어 전달되므로 

변수  num 이 아닌  num 의 값을 주소로 보고 거기에 입력받은 값을 저장하게 된다.

 

 

다음은 참고 사항이므로 읽지 않아도 좋다.

사실 지금 읽으면 헷갈릴 수 있으니 나중에 읽는 것이 좋다.

 

엄밀히 말하면 C언어에는 참조라는 개념이 없다. call-by-reference는 호출 함수의 변수를 피호출 함수가 자신의 매개변수 이름으로 직접 접근하는 방식이다. 그러나 C언어에서는 변수의 값을 복사해서 받아오므로 이것이 불가능하다.

따라서 주소값도 실제로는 복사되어 전달된다. 주소값도 어쨌든 값이기 때문이다.

그러니 앞에서 CBR로 소개한 것도 사실은 CBV인 것이다. 다만 간접 참조 연산(* 연산)으로 CBR을 흉내 낼 뿐이다.

 

 

 

 

2. 매개변수로 배열 전달하기

 

매개변수에 배열을 전달하려면 어떻게 해야 될까?

우선 배열을 통째로 전달할 수는 없다. 매개변수에 배열을 선언할 수 없기 때문이다.

따라서 배열의 시작주소를 전달하고 그를 기준으로 배열의 요소에 접근하는 방법은 생각해볼 수 있다. 그러니까 다음과 같은 형태로 함수를 호출하는 것이다.

 

int arr[3] = { 1, 2, 3 };

 

printArray(arr);

 

배열의 이름은 배열의 시작주소, 즉 주소값이다. 그리고 주소값은 포인터 변수에 저장된다.

따라서 매개변수를 포인터형으로 선언해주면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
 
void printArray(int* a);
 
int main()
{
    int arr[4= { 3679 };
 
    printArray(arr);
 
    return 0;
}
 
void printArray(int* a)
{
    int i;
 
    printf("{ %d", a[0]);
    for (i = 1; i < 4++i)
    {
        printf(", %d", a[i]);
    }
    printf(" }");
}
cs

 

 

 

 printArray  함수로 전달되는 것은 배열 arr의 시작 주소다. 

따라서 이 주소를 기준으로 배열의 요소들을 참조할 수 있는 것이다.

그렇다면 배열의 값을 변경하는 것도 가능하지 않을까?

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
 
void modifyArray(int* a, int index, int value);
void printArray(int* a);
 
int main()
{
    int arr[5= { 13579 };
    int idx;
    int val;
 
    printf("현재 배열 상태: ");
    printArray(arr);
 
    printf("수정할 인덱스: ");
    scanf("%d"&idx);
 
    printf("새로 저장할 값: ");
    scanf("%d"&val);
    
    modifyArray(arr, idx, val);
 
    printf("변경 후 배열 상태: ");
    printArray(arr);
 
    return 0;
}
 
void modifyArray(int* a, int index, int value)
{
    a[index] = value;
}
void printArray(int* a)
{
    int i;
 
    printf("{ %d", a[0]);
    for (i = 1; i < 5++i)
    {
        printf(", %d", a[i]);
    }
    printf(" }\n");
}
cs

 

 

이처럼 배열의 주소만 안다면 배열의 접근해 값을 참조 및 변경할 수 있다.

 

참고로 배열의 주소를 매개변수로 전달할 때는 다음과 같은 선언도 가능하다.

 

1
2
3
4
5
6
7
8
9
10
11
void printArray(int a[])
{
    int i;
 
    printf("{ %d", a[0]);
    for (i = 1; i < 5++i)
    {
        printf(", %d", a[i]);
    }
    printf(" }\n");
}
cs

 

 int a[] 는  int* a 와 완전히 동일한 선언이다.

다만 전자일 때가 배열이 전달된다는 느낌이 강해, 보통 배열을 매개변수로 전달할 때는 

전자의 방법을 사용한다.

어느 방법을 써도 좋지만 주의할 점은 함수 내에서 배열의 길이를 계산할 수 없다는 것이다.

배열의 길이는 다음과 같이 계산한다.

 

TYPE arr = ...;

sizeof(arr) / sizeof(TYPE);

 

 sizeof(arr) 이 배열이 차지하는 메모리 공간의 크기를 반환하므로, 

이를 배열의 자료형 크기로 나누면 배열의 길이가 되는 것이다.

하지만 이는 배열이 선언된 지역에서만 가능하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
 
void printArray(int a[]);
 
int main()
{
    int arr[5= { 13579 };
   
printArray(arr);
 
    return 0;
}
 
void printArray(int a[])
{
    int i;
    int len = sizeof(a) / sizeof(int);
 
    printf("{ %d", a[0]);
    for (i = 1; i < len++i)
    {
        printf(", %d", a[i]);
    }
    printf(" }\n");
}
cs

 

17행에서  len 에 저장되는 값은  a 의 크기를  4 로 나눈 값이다.

그런데  a 는 포인터 변수이므로 크기는 4바이트(또는 8바이트)다.

따라서 여기서는 배열의 크기가 아니라 포인터 변수의 크기가 반환된다.

 

함수에서 배열의 크기를 참조하려면 다음과 같이 함수를 정의하고 호출해야 한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
 
void printArray(int* a, int length);
 
int main()
{
    int arr[5= { 13579 };
    int len = sizeof(arr) / sizeof(int);
 
    printArray(arr, len);
 
    return 0;
}
 
void printArray(int* a, int length)
{
    int i;
 
    printf("{ %d", a[0]);
    for (i = 1; i < length; ++i)
    {
        printf(", %d", a[i]);
    }
    printf(" }\n");
}
cs

 

 

'Programming > C' 카테고리의 다른 글

다차원 배열  (0) 2019.04.01
다중 포인터  (0) 2019.03.24
포인터와 배열의 관계  (0) 2019.03.17
포인터  (0) 2019.03.16
1차원 배열  (0) 2019.03.15