[C언어] 포인터의 연산과 인수 전달 방법

2024. 3. 29. 02:35CS/C언어

728x90

포인터의 연산

포인터끼리의 연산

https://cascade.tistory.com/67

C언어에서 포인터(pointer)의 개념

C언어에서 포인터란, 메모리의 주소 값을 저장하는 변수이다. 포인터는 아래와 같이 선언한다. int n = 150; int *p = &n; 정수 값 150은 n이라는 변수 안에 저장되며, 이는 메모리 상에 어떤 공간에 할당

cascade.tistory.com

앞선 포스팅에서 정의한 포인터를 살펴 보자.

int n = 150;
int *p = &n;

 
여기서의 포인터 변수 p는 어떻게 연산할까? 포인터도 정수로 표현이 가능하므로 int 자료형처럼 더하기, 빼기, 곱하기, 나누기 연산이 가능할까? 두 정수 m, n을 선언하고 아래 코드를 실행해 보자.

#include <stdio.h>

int main() {
    int m = 100;
    int n = 150;
    int *p1 = &m;
    int *p2 = &n;
    printf("%d\n", p1);
    printf("%d\n", p2);
    printf("%d\n", p1+p2);
    printf("%d\n", p1-p2);
    printf("%d\n", p1*p2);
    printf("%d\n", p1//p2);
    
    return 0;
}

 
위를 실행하면 아래와 같은 결과가 나온다. 

 
포인터끼리의 더하기, 곱하기, 나누기 연산은 모두 에러를 발생시킨다!! 그럼 빼기는 정상 작동하는 것일까? 위 코드에서 아래와 같이 빼기만 남기고 다시 실행해보자.

int main() {
    int m = 100;
    int n = 150;
    int *p1 = &m;
    int *p2 = &n;
    printf("%d\n", p1);
    printf("%d\n", p2);
    printf("%d\n", p1-p2);
    
    return 0;
}

 
실행 결과는 아래와 같이 나온다. m 값은 189811388이라는 메모리에, n값은 189811384에 저장되었으며 두 메모리의 포인터 차이는 1이다. 그런데 이 두 메모리 주소값의 차이는 누가 봐도 4인데, 왜 1이 출력된 것일까? 

 
포인터의 빼기 연산의 결과는, 메모리 주소값의 차이 그 자체가 아니라, 메모리 주소값의 차이를 자료형의 크기로 나눈 값이 되기 때문이다. 즉 이 경우는 메모리 주소값의 차이는 4, int 자료형의 크기도 4byte이므로 두 포인터의 빼기 연산 결과가 4/4 = 1로 나온 것이다.
 
또한, 포인터끼리는 아래와 같이 비교연산도 가능하다. 

#include <stdio.h>

int main() {
    int m = 100;
    int n = 150;
    int *p1 = &m;
    int *p2 = &n;
    if (p1<p2){
        printf("yes!");
    }
    else{
        printf("no!");
    }
    
    return 0;
} //실행결과: no

 
 

포인터와 정수의 연산

 
주소 0을 가리키도록 정의한 포인터 p를 1 증가시키면 어느 주소를 가리킬까? 아래 코드를 실행해보자.

#include <stdio.h>

int main() {
    int *p = 0;
    printf("%d", ++p);
}

 
실행 결과 4가 나온다. 그 이유는, p에서 +1이 늘어난 것을 정수 하나의 크기(4 byte)만큼으로 생각하기 때문이다. 만약 포인터가 char의 자료형에 대해 정의되었다면 가리키는 메모리는 1 증가했을 것이고, 마찬가지로 double은 8 증가했을 것이다.
 

인수 전달 방법

함수 호출 시, 괄호 안에 들어가는 데이터들을 인수(arguments, args)라고 한다. 이때 함수 안에 들어가는 변수는 메모리 상 어디에 저장될까? 함수가 인수를 불러오는 방식은 크게 두 가지가 있다.

  1. Call by Value: 인수로 들어온 원본을 복사하여 함수 안에 있는 매개변수로 넣는 방식으로, 함수 안에서 해당 매개변수 값이 바뀌어도 원본은 바뀌지 않는다.
  2. Call by Reference: 인수로 들어온 원본의 참조 값(메모리상 위치)을 함수 내부로 넣는 방식으로, 함수 안에서 해당 값을 바꾸면 원본도 바뀐다.
Call by Value

 
아래 코드의 main 함수에서, x = 0이라는 정수 값이 main에서 저장되는 참조값과 function에 호출되어 저장되는 참조값은 다르다. 왜냐하면 function은 x=0을 복사하여 새로운 메모리에 할당했기 때문이다.

#include <stdio.h>

int function(int num){
    int* p = &num;
    return p;
}

int main() {
    int x = 0;
    int* p = &x;
    printf("%d, %d", p, function(x));
}

 
실제로 이를 실행해보면 아래와 같이 출력된다.

 
두 주소값을 비교해 보면 다르다는 것을 알 수 있다. 기본적으로 C언어에 존재하는 자료형(int, char, float, enum...) 등은 모두 Call by Value 방식으로 함수에 전달된다. 즉 함수에 인수로 전달되어 매개변수가 된 후 수정을 거쳐도 원본이 바뀌지 않는다.
 

Call by Reference

 
C에서 array를 함수의 인자로 넣을 때, 모든 array의 value들을 인자로 넣지는 않는다. 대신, array의 첫 원소의 참조값을 함수의 인자로 넣는다. 이처럼 함수의 인자로 주소 값이 들어가는 경우를 Call by Reference라고 한다. 아래 코드를 실행해 보자.

#include <stdio.h>


void function(int arr[]) {
    arr[0] += 1;
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);
    
    # 첫 번째 배열 출력
    for(int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    function(arr);

    # 두 번째 배열 출력
    for(int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

 
arr를 인수로 받는 함수 function에 대해, arr를 함수에 전달하기 전, arr를 함수에 인자로 전달, arr를 함수에 전달한 후의 세 부분으로 main함수를 구성하였다. 이의 실행 결과는 아래와 같다.

 
main 함수에서는 arr를 고치는 코드가 하나도 없었고, 함수를 호출할 때 인수로 사용하기만 했는데도 arr[0]에 변화가 생긴 걸 볼 수 있다. arr가 참조하는 주소는 function 안과 밖에서 같으므로, function에서 arr에 가한 변화가 바깥에서도 적용되는 것이다. 이러한 문제는 의도치 않은 데이터 변화를 가져올 수 있는데, 이를 방지하기 위해 arr에 있는 모든 원소를 복사하는 deep copy를 수행하기도 한다.

반응형

'CS > C언어' 카테고리의 다른 글

C언어에서 포인터(pointer)의 개념  (1) 2024.03.28