코드 여행자

포인터 기본2- call by reference 본문

포인터(Pointer) 변수

포인터 기본2- call by reference

코드여행자12 2019. 11. 3. 16:41

포인터 변수 사용 이유



이전 글에서 * 기호를 사용하여 포인터 변수가 가지고 주소에 있는 값을 역참조할 수 있다는것을 알았습니다.

중요한 것은 그 주소에 있는 값을 변경할 수 있다는 것입니다.



코드 1

   
#include<stdio.h>

int main()
{
    int a = 100;   // 1)
    int *temp = &a;   // 2)

    printf("address of a : %p\n",&a);
    printf("value of temp : %p\n",temp);  // 3)
    printf("dereference of temp : %d\n",*temp);   // 4)

    *temp = 1000;  // 5) 중요

    printf("value of a : %d\n",a);  // 6)
    printf("dereference of temp : %d",*temp);  // 7)
        
    return 0;
}

실행 결과

address of a : 0x7ffee3205b38

value of temp : 0x7ffee3205b38

dereference of temp : 100

value of a : 1000

dereference of temp : 1000 


1) 정수형 변수 a에 100을 저장하고 정수형 포인터 변수 temp에 변수 a의 주소를 대입하였습니다.

2) 그리고 &a 를 사용하여 변수a의 주소를 확인하였습니다.

3) temp는 변수 a의 주소를 저장하고 있으므로 &a와 같은 값을 출력합니다.

4) * 기호는 주소에 있는 값을 접근할 수 있기 때문에 *temp를 사용하면 변수 a주소에 있는 값을 확인할 수 있습니다.(즉, 100)

여기까지는 이전 포스팅에서 확인했던 내용입니다.


5) 다음으로 *temp = 1000; 이 코드는 어떻게 동작할까요?

쉽게 표현하면 "temp 포인터 변수가 가리키는 주소에 저장된 값을 1000으로 변경한다" 입니다.

또는 "temp 포인터 변수가 가리키는 주소에 1000을 write한다"고 말할 수도 있습니다.

temp는 주소값을 의미(포인터 변수이므로)하고 *temp는 정수값을 의미합니다.(정수형 포인터이므로)

*temp가 어떤 값이었는지 관계없이 1000 이라는 정수값을 대입(=) 하겠다는 것입니다. 

즉, 이제 *temp는 1000이다 라는 뜻입니다. (temp 가 가리키는 주소의 값을 역참조해보면 1000)


그림 1을 보면 정수형 변수 a와 포인터 변수 temp에 저장된 값과 두 변수의 메모리 관계를 볼 수 있습니다.

그림 2는 *temp = 1000 코드를 실행 한 이후 메모리 상태를 표현하고 있습니다.


포인터를 이용한 값 변경

6) 변수 a의 값을 출력해보면 100이 아니라 1000으로 바뀐것을 확인할 수 있습니다. 이는 temp 포인터 변수를 이용하여 *temp값을 변경했기 때문입니다.

변수 a메모리 주소를 접근하여 값을 변경해버리면 변수 a 값도 영향을 받는것은 당연한 것입니다.

7) 당연히 *temp 값을 출력해보면 1000이 표시됩니다.( a == * temp)


포인터 변수 사용 이유

포인터 변수는를 사용하면 어떤 장점이 있을까요?

포인터 변수는 값이 저장된 메모리를 직접적으로 접근할 수 있습니다. 

위의 예제에서 *temp = 1000 코드를 통해서 메모리에 값을 변경하는 것까지 확인하였습니다.


어떤 일을 수행하는 함수를 만들 때, 함수가 수행한 결과를 반환받고 싶을 때가 있습니다.

그 값이 1개라면 아래와 같이 간단하게 구현할 수 있습니다.


코드 2

   
#include<stdio.h>

int add(int a, int b)
{
    return a+ b;
}

int main()
{
    int result = 0;

    result = add(1,2);
    printf("result = %d\n",result);

    return 0;
}


실행 결과

result = 3 


첫 번째 인자와 두번째 인자를 더해서 반환하는 add라는 간단한 함수를 실행한 코드입니다.

이때 함수에 전달하는 인자 a,b는 1,2 정수값을 직접 전달합니다. 그리고 결과 값은 함수의 반환 값으로 받습니다.

좀더 자세히 설명하면 add 함수는 인자 2개를 가지고 있고 이 함수를 사용할 때 1,2 값을 전달하였습니다.

add 함수가 실행될 때 시스템은 스택 메모리에 int a, int b를 할당합니다. 그리고 a에는 1를 저장하고 b에는 2를 저장합니다.

이렇게 함수를 호출할 때 인자에 값을 전달하는 것을 call by value라고 합니다.


위의 예제 코드와 달리 포인터 변수를 추가로 사용하여 add 함수를 만들어 보겠습니다.

add 함수에 인자로 포인터 변수를 추가하여 결과 값을 받을 수 있습니다.


코드 3

#include<stdio.h> int add(int a, int b, int * result) { if(a < 0 || b < 0) { return -1; // invalid parameter } *result = a + b; // 3) 중요 return 0; // success } int main() { int resultValue = 0; int returnValue = 0; // 1) returnValue = add(1,2,&resultValue); // 2) printf("a+b : %d\n",resultValue); // 4) printf("returnValue = %d\n",returnValue); // 5) return 0; }

실행 결과

a+b : 3

returnValue = 0 


 이번에 구현한 add 함수는 포인터 변수 result를 이용하여 a+b 값을 가져올 수 있을 뿐 아니라 add 함수의 return 값을 사용하여 인자가 정상적인지 add함수가 성공적으로 실행되었는지를 알려줄 수 있습니다.(예를들어 add 함수가 나이를 더하는 동작을 한다면 인자로 음수가 들어오면 안되겠죠)


1) returnValue 변수는 add 함수가 정상적으로 실행되었는지 여부를 판단할 수 있는 값을 반환 받습니다.


2) 정수형 변수 resultValue는 add 함수의 세번째 인자로 전달이 됩니다. 이때 & 기호를 사용합니다.

그 이유는 add 함수의 세번째 인자는 주소 타입을 받는 포인터 변수로 선언되어있기 때문입니다.

&기호를 사용하면 해당 변수의 주소를 의미한다는 것을 이전 포스팅을 통해 알고 있을 것입니다.

세번째 인자인, 포인터 변수 result는 resultValue 변수의 주소를 가리키게 됩니다. 

세번째 인자는 값을 전달한 것이 아니라 변수를 접근할 수 있는 주소를 전달하였기 때문에 (변수를 reference할 수 있는 주소를 전달) call by reference라고 합니다.


3) add 함수 내부의 *result = a+b; 코드가 어떻게 동작되는지 아래 그림으로 확인해보겠습니다. 


그림 3은 returnValue = add(1,2,result); 코드가 실행되기 전의 메모리 상태입니다.

메모리상에는 resultValue와 returnValue 변수만 존재합니다.

각 변수는 0으로 초기화된 것을 볼 수 있습니다.


그림 4는 returnValue = add(1,2,result); 코드가 실행 되어 add 함수가 실행된 직후의  메모리 상태입니다.

시스템이 add 함수를 실행하게 되면  stack에 add 함수의 인자 3개에 대한 변수를 할당하게 됩니다.

즉, int a, int b, int* result 일반 변수2개, 포인터 변수1개가 스택 메모리상에 잡히게 됩니다.


*result = a+b; 코드가 실행되면 resultValue의 값은 (그림 4의 잴위 동그란 부분) 3으로 변경됩니다. 

*result가 존재하는 위치에 3이 write되는 것입니다.


4) resultValue를 출력하면 add 함수가 계산한 값3을 넣을 수 있습니다.

5) returnValue를 확인하면 0입니다. 만약, -1을 린턴받았다면 add 함수의 첫번째, 두번째 인자에 문제가 있다는 의미입니다.


call by reference



코드 2와 코드 3, 두 예제는 call by value와 call by reference의 차이점이기도 합니다.

포인터를 사용하는 이유는 직접 메모리를 접근하여 원하는 값으로 변경이 가능하다는 것입니다.

위 예제는 하나의 포인터 변수를 사용하여 값을 변경하였지만 배열과 포인터를 함께 사용한다면 다수의 값을 read/write 할 수 있어 편리합니다.


어떤 함수가 배열에 있는 데이타를 변경해야 한다면 2가지 방식 중 어떤 방식을 사용해야 좋을까요?

함수는 배열을 전달 받아야 합니다. 이때 call by value로 전달받으려면 배열 사이즈 만큼의 메모리가 더 필요합니다. 

그리고 배열의 값들을 복사하는 작업도 필요할 것입니다.


하지만 call by reference로 배열의 주소만 전달 받는다면 포인터 변수 사이즈만큼만 메모리 사용이 추가될 것입니다.

따라서 포인터를 사용하면 메모리 공간을 아낄수 있으며 값을 복사하는 과정이 없으므로 성능면에서도 이득이 됩니다.


앞으로 포인터에 관련한 글들을 더 포스팅 할 예정입니다.

이 글에서 다 언급하지 못했던 포인터를 사용하는 이유들이 자연스럽게 설명될 것으로 생각됩니다.







'포인터(Pointer) 변수' 카테고리의 다른 글

배열과 포인터(Array and Pointer)  (0) 2019.11.11
포인터(Pointer) - 기본1  (0) 2019.10.20
Comments