포인터

김 무무 ㅣ 2024. 4. 14. 21:15

1. 개요

같이 공부하는 사람들의 질문을 받다 보니 배열을 정확하게 이해하지 못하는 사람이 많다는 것을 느꼈다.

그래서 먼저 변수에 대해 간단하게 설명하고, 포인터에 대해 간단하게 소개하겠다.

그 후 배열이 정확하게 어떤 것인지 적어보고자 한다.

 

편의를 위해 이번 글에서는 int를 4바이트, 변수의 주소를 100으로 가정했다.

 

 

 

2. 변수(Variable)

포인터를 이해하기 위해서는 우선 변수가 어떤 식으로 메모리에 저장되는지 알아야 한다.

 

코드 :

int a = 50; // 선언
printf("a\t = %d\n", a); // 출력

 

실행 결과

 

위 코드를 순서대로 해석해 보면

 

< 선언 >

  1) 메모리에서 4바이트 크기를 묶는다.

  2) 묶은 메모리에 a라는 이름을 붙인다.

  3) a에 정수타입으로 50이라는 값을 저장한다.

< 출력 >

  4) 변수 a를 찾아 50을 출력한다.

 

 

변수를 선언했을 때 메모리의 100번지부터 int의 크기(4바이트)만큼 메모리를 묶어 a라는 이름을 붙이고, 50이라는 값을 저장했다.

그리고 값을 출력할 때 변수의 이름을 사용해 그 변수에 저장된 값을 출력했다.

 

즉, 기존의 방식은 변수의 이름으로 값을 찾는다.

 

 

 

3. 주소연산자(Address operator) &

다음은 주소연산자 &에 대해 알아보겠다.

&기호는 관계연산자나 비트연산자로 쓰이기도 하지만 변수이름 앞에 붙으면 주소연산자로 기능한다.

 

코드 :

int a = 50;
printf("a\t = %d\n", a);
printf("&a\t = %d\n", &a); // 추가

 

실행 결과

 

코드의 마지막줄에 &a를 출력하는 코드를 추가했다.

 

기존의 코드는 a라는 변수명을 찾아 값을 출력했다. 

새롭게 추가된 코드는 a라는 변수명을 찾아 a가 저장된 주소를 출력하는 코드이다.

 

 

즉, 주소연산자는 변수의 이름으로 그 변수가 저장된 주소를 찾는다.

 

 

 

4. 포인터(Pointer) *

다음은 포인터에 대해 알아보겠다.

포인터를 선언할 때는 변수명 앞에 * 기호를 붙인다.

* 기호를 붙이면 해당 변수는 포인터 변수가 되며 주소를 저장하는 변수로 만들어진다.

 

코드 : 

int a = 50;
printf("a\t = %d\n", a);
printf("&a\t = %d\n\n", &a);

// 추가
int *ptr; // 선언
ptr = &a; // 대입
printf("ptr\t = %d\n", ptr); // 출력

 

실행 결과

 

새롭게 추가한 코드를 해석하면

 

< 선언 >

  1) 메모리에서 4바이트 크기를 묶어 포인터 변수로 만든다.

  2) 묶은 메모리에 ptr이라는 이름을 붙인다.

< 대입 >

  3) ptr에 변수 a의 주소를 저장한다.

< 출력 >

  4) ptr에 저장된 값을 출력한다.

 

 

변수를 선언할 때 *기호를 붙여 주소를 저장하는 포인터 변수를 생성하고, a의 주소를 저장했다.

ptr의 값을 출력하면 a의 주소가 출력된다.

 

 

 

5. 간접 참조 연산자(Indirect Operator) *

사실 포인터를 처음 배울 때는 포인터와 간접참조 연산자를 동일하게 생각하는 경우가 많다.

하지만 포인터를 선언할 때의 * 기호와 간접참조 연산자로 쓰이는 * 기호는 의미가 다르다.

 

코드 : 

int a = 50;
printf("a\t = %d\n", a);
printf("&a\t = %d\n\n", &a);

int *ptr;
ptr = &a;
printf("ptr\t = %d\n", ptr);
printf("*ptr\t = %d\n", *ptr); // 추가

 

실행 결과

 

기존의 코드는 ptr에 저장된 값인 a의 주소(100)를 출력했다.

*ptr은 ptr에 저장된 주소(100)를 찾아 그곳에 저장된 값(50)을 출력한다.

 

 

즉, 이때 사용한 * 기호는 주소로 값을 찾는다.

이 과정을 역참조(Dereference)라고 한다.

 

 

 

6. 포인터의 자료형

지금까지 포인터의 사용과 작동 방식에 대해 설명했다.

그런데 여기서 오해하기 쉬운 포인터의 사용방식에 대해 먼저 바로잡겠다.

 

코드 : 

// 잘못된 방식
char a = 50;
int *ptr = &a;

// 정상적인 방식
int a = 50;
int *ptr = &a;

 

위 코드에서 포인터 변수의 자료형을 int로 선언했다.

 

기존의 방식대로면 위 선언은 변수에 값을 int로 저장하겠다는 의미가 된다.

그런데 여기서는 포인터 변수에 값을 int로 저장하겠다는 뜻이 아닌 역참조 할 주소에서 int형으로 값을 읽어오겠다는 의미를 갖는다.

 

그 이유는 포인터 변수가 오직 메모리 주소만을 저장하기 위한 변수이기 때문이다.

메모리 주소의 크기는 운영체제에 따라 4바이트나 8바이트로 크기가 이미 정해져 있다.

그래서 굳이 포인터 변수를 선언할 때마다 일일이 저장할 메모리 주소의 크기를 지정해주지 않아도 자동으로 정해진 크기에 맞춰서 크기가 결정된다.

 

 

앞서 말했듯 포인터 변수를 선언할 때의 자료형은 포인터 변수에 저장된 주소로 찾아가 선언한 자료형인 int(4바이트)만큼 데이터를 사용하겠다는 의미이다.

이때 그 주소에 저장된 데이터의 자료형과 포인터 변수의 자료형이 다르면 저장된 변수를 잘못 읽어오게 된다.

 

잘못 읽어온 데이터와 정상적으로 읽어온 데이터

 

위 코드의 실행 결과를 표로 정리했다.

char는 1바이트만큼 값을 저장하기 때문에 포인터를 int자료형으로 지정하면 저장된 값과 다른 데이터를 읽어온다.

int는 4바이트만큼 값을 저장하기 때문에 포인터가 정확한 값을 읽어온다.

 

그러니 변수의 자료형과 포인터 변수의 자료형을 일치시켜야 값을 정상적으로 읽어올 수 있다.

 

 

 

8. 마무리

포인터는 값을 바로 사용하는 것이 아닌 주소를 찾아가 값을 사용한다.

이런 방식으로 인해 Call by value와 Call by reference의 차이가 생겨난다.

 

코드 : 

#include <stdio.h>
void val(int a) { a++; }
void ref(int* a) { (*a)++; }

int main() {
	int a = 50;

	val(a);
	printf(" Call by value\t= %d\n", a);

	ref(&a);
	printf(" Call by reference\t= %d\n", a);
}

 

Call by value는 변수의 값을 전달해 그 값을 수정한다.

그래서 값을 수정해도 원본 값이 바뀌지 않고 전달된 값만 변경된다.

 

Call by reference는 값이 아닌 변수의 주소를 전달해 그 주소에 저장된 값을 수정한다.

그렇기 때문에 값을 수정하면 원본값이 바로 변경되는 것이다.

 

 

 

 

+ 추가

나중에 해당 내용을 따로 다룰지 아닐지 몰라서 생각난 김에 적어두려고 한다.

 

수업에 사용되는 책과 여기서 말한 내용이 달라 조금 더 확실하게 설명하겠다.

사실 위에서 말한 방식은 Call by reference라고 하기 애매한 방식이다.

 

앞서 Call by value는 값을 전달한다고 설명했다.

그런데 주소를 전달하는 방식도 결국 '주소'라는 값을 전달받는다.

 

한마디로 위에서 설명한 방식은 Call by value로 값을 받아와 Call by reference처럼 값을 수정한다는 뜻이다.

이 방식은 Pass by address라는 방식으로 분류된다.

그리고 C++에서 레퍼런스 변수라는 새로운 변수가 나오는데, 이 레퍼런스 변수를 이용한 방식이 값을 전달하지 않는 정확한 Call by reference이다.

 

 

이 글은 포인터를 처음 배우는 사람을 대상으로 한 글이라 사실 이건 헷갈리면 당장 기억할 필요는 없다.

수업에 사용하는 책에서 Call by reference를 설명할 때 포인터가 아니라 이 레퍼런스 변수를 사용하길래 추가로 적어봤다.

 

잘 모르겠으면 대충 이런 내용이 있었다는 것만 기억해 뒀다가 C++을 배우고 레퍼런스 변수를 배우면 그때 이 차이를 다시 생각해 봐도 된다.

 

 

 

 

틀린 내용이 있다면 댓글로 적어주세요.

'생각 정리' 카테고리의 다른 글

레퍼런스 변수와 포인터 변수  (0) 2024.04.29
재귀함수  (0) 2024.04.28
배열  (0) 2024.04.22
포인터 연산  (0) 2024.04.21
메모리  (0) 2024.04.07