C/C++ 포인터 한 페이지로 정리하기

logorealfinal

오늘은 포인터의뒤죽박죽 정리된 개념을 제대로 정립하실 수 있도록 도와드리려고 개념에 집중한 글을 작성해봅니다.

혹은, 아직 공부 단계거나, C/C++ 언어의 포인터의 악명을 듣고 검색해보신 분들도 이 한페이지를 통해서 개념을 확실히 잡으실 수 있도록 도와드리겠습니다.

(쉬운 설명을 위해 비유적 표현을 일부 사용하기도 하고, 단순하게 요약한 부분도 존재합니다. 다만, 내용이 아예 틀린 경우는 지적해주시면 바로 수정하겠습니다.)

 


Prerequisite

 

선행 학습 부터 시작하겠습니다. 이 파트에 나오는 내용은 바로 이해가 가지 않더라도 미리 읽어놓으신 후, 본문을 진행하면 이해가 더 빠를 것으로 생각합니다. 코드를 들여다보는 것은 본문에서 진행됩니다!

포인터는 메모리 주소를 가지고 노는 것으로 생각하시면 편합니다. 일반적인 변수 ‘a’와 포인터형 변수 ‘x’를 선언합니다. 그리고 ‘a’에 ’42’라는 숫자를 대입합니다. 이 경우, 메모리의 어떤 주소(사실은 ‘a’ 변수의 주소)에 가서 값을 확인해보면 ’42’라는 숫자가 적혀있습니다. 이 때, 이 메모리 주소를 얻기 위해 ‘&a’라는 표현을 사용합니다. 따라서, 아까 포인터형 변수로 쓰겠다고 선언해둔 ‘x’에 이 주소값을 대입하면, 포인터형 변수 ‘x’를 통해 ’42’라는 숫자에 자유롭게 접근할 수 있게 됩니다.

우선, 포인터 파트에서 가장 중요한 것은 ‘&’, ‘*’ 연산자이죠. 연산자입니다. 연산자는 둘 이상의 피연산자(연산을 당할 것들)에 대해 수행할 평가(행동)를 지정합니다.[0]

연산자의 경우 크게 이항 연산자와 단항 연산자로 나뉩니다.

이항 연산자는 전,후로 두 개의 변수 등을 필요로 하는 것으로, 예를 들어, 2 + 3 에서 ‘+’의 역할과 같습니다. 즉, 이항 연산자는 쓰일 때 반드시 앞, 뒤의 관계에서 작용하므로 두 개 이상의 ‘무언가’를 필요로 합니다.

단항 연산자는 앞이나, 뒤에 붙어서 한 가지의 변수, 상수만을 필요로 하는 것으로서, a++ 에서 ‘++’의 역할과 같습니다. 즉, 무엇이 되었든 한 가지와 결합하여 그 것을 변형하거나 수정하는 경우에 쓰입니다.

이어서, ‘&’는 ‘무엇무엇의 주소(Address-of)’, ‘*’는 ‘간접 참조’의 의미를 갖고 있습니다. (영어 원서 등으로 공부하신 분들은, ‘*(asterisk)’의 경우에 ‘dereference’ 기능을 갖고 있다고 자주 보셨을 텐데, 이 ‘dereference’의 의미가 ‘단항연산자(unary operator)로 쓰일 경우, ‘해당 아이템의 주소값을 통하여, 다른 장소의 데이터를 얻다.(obtain from (a pointer) the adress of a data item held in another location)’ 입니다. 재밌는 것은 해당 정의에서 쓰인 ‘adress of’ 부분만 슬며시 ‘&’로 바꾸면, 프로그래밍 언어 C/C++의 문법에서 쓰이는 것과 유사해진다는 점입니다.

 


Main

 

자 이제 본문에 들어가겠습니다. 아주 익숙한 선언들로부터 시작하겠습니다. 본문 내용은 온라인 컴파일러, 혹은 본인의 작업 환경에서 따라 진행하시는 것이 학습에 훨씬 큰 도움이 됩니다. 제발, 읽고 넘어가지 마세요!!!
(일부, 프로그래밍 서적에서 ‘포인터 변수’로서 사용할 변수라는 의미로 변수 명을 ‘pointer’등으로 설명하여 혼란을 가중시키는 것으로 판단, 여기서는 일반 변수처럼 선언하겠습니다.)

정수형 변수 a와 b를 선언하고 싶습니다.

/* 이 부분은 아래 예시들에선 생략합니다.
 * 추가로, 이전에 이어서 작성하시면 됩니다.
 * #include <stdio.h>
 *
 * int main(){
 */

int a;
int b;

 

그렇다면 포인터로 쓰일 변수 x는 어떻게 선언할까요. 아래와 같습니다.

int *x;   // 여기서 쓰인 '*'은 'x'라는 변수가 포인터라는 것을
          // 알리기 위함으로 쓰입니다.

// 포인터형 변수를 선언할 당시의
// '*'의 위치는 중요하지 않습니다. 즉,
int* v1;
int * v2;
int *v3;
// v1,v2,v3 모두 포인터형 변수를 선언해주었습니다.

// 그러나, 아래와 같은 경우에 실수가 잦아 보통은 
// 변수에 붙여서 '*'를 사용합니다.
int* v4, v5; 
// 이 경우, v5는 int형 변수, v4은 포인터로 선언됨.

 

위에서 선언하였던, a와 b에 각각 아무 숫자나 원하는 숫자를 대입하도록 하겠습니다.(여러분들도 변형하여 따라해주시면 됩니다.)

a = 42;
b = 27;

자 이제, 주소값에 접근해보도록 하겠습니다. 선행 학습에서 말씀드렸다시피, 무엇무엇의 주소를 얻고 싶을 때는, ‘address of’ 연산자인 ‘&’를 활용합니다. 중요한 점은, 주소값을 포인터형으로 선언한 변수에 대입하여야 이후에 ‘*’연산자를 통해 가리키는 지점에 접근할 수 있다는 점입니다.

// 위에서 잔뜩 만들어보았던, v1, v2를 활용하겠습니다.
v1 = &a; // v1 변수는 address of a;
v2 = &b; // v2 변수는 address of b;

자 이제 확인을 위해 몇 가지 출력 테스트를 해보도록 하겠습니다.

printf("value of a: %d\n", a);
printf("value of b: %d\n", b);

printf("value of &a: %p\n", &a);  //여기서부터 %p
printf("value of &b: %p\n", &b);

printf("value of v1: %p\n", v1);
printf("value of v2: %p\n", v2);

결과값을 확인해볼까요? ( 정확한 메모리 주소값은 환경에 따라 다를 수 있습니다.)

value of a: 42
value of b: 27

value of &a: 0x7ffdfd08bb58
value of &b: 0x7ffdfd08bb5c

value of v1: 0x7ffdfd08bb58
value of v2: 0x7ffdfd08bb5c 

a의 주소값(&a)과 v1 변수 자체가 지니고 있는 값이 같음을 알 수 있고, 마찬가지로 b의 주소값(&b)과 v2 변수 자체가 지니고 있는 값이 같음을 알 수 있습니다. 대입연산자(‘=’)를 통해 대입하였으니 당연한 결과입니다.

 

자 그럼 선행학습 때 배운, ‘*’연산자를 통해 포인터 변수가 가진 주소값이 가리키는 곳에 접근해볼까요?

printf("v1 is pointing %d\n", *v1);
printf("v2 is pointing %d\n", *v2);

결과값은 아래와 같습니다.

v1 is pointing 42
v2 is pointing 27

즉, 각 포인터형 변수가 가지고 있던 메모리 주소값이 가리키는 그 곳의 값을 ‘*(포인터형 변수)’를 통해 알 수 있습니다. 따라서, ‘*’ 연산자는 ‘포인터형 변수’ 앞에 붙어, 이 뒤에 따라붙은 ‘포인터형 변수’의 주소값에 직접 가서 뭐가 있는지 살펴보자 라는 뜻이 됩니다.

자 그럼 한 단계 더 진행해보도록 하겠습니다.

int **v0; //별이 두 개 붙은 포인터형 변수 'v0'를 선언

v0 = &v1;
printf("v0 is pointing %p\n", *v0);
printf("pointee of v0 is pointing %d\n", **v0);

결과값을 보면,

v0 is pointing 0x7ffc63743288
pointee of v0 is pointing 42

라고 나옵니다.

즉, 포인터형 변수 v0 는 포인터를 가리키는 변수라는 의미로 **v0로서 선언한 후, v0에 다른 포인터 변수의 주소값을 넣어 줍니다. 따라서, v0를 한번 역참조(*v0, dereference)할 경우, v1의 결과값(0x7ffc63743288, &a의 값)이 나오고, 이 것을 한번 더 역참조(**v0)하면, 결국 a의 값을 확인할 수 있게 됩니다.


Conclusion

정리하면 아래와 같습니다.

int a; //정수형 변수가 있다.
int *p; //포인터형 변수가 있다. (여러분들을 머리아프게 하는
        //부분은 바로 여기 쓰인 '*'입니다.
        //여기서의 '*'은 단순히 뒤에 있는 'p'라는 변수가
        //포인터형 변수다라는 것을 알리기 위함일 뿐입니다.)
        //따라서, 아래의 모든 선언이 포인터형 변수를 만듭니다.
int* p1;
int * p2;
int *p3; 
int *p4, *p5, *p6;
int *p7, *p8, p9; //여기서 p9은 정수형 변수.

a = 19; //a에 19를 대입

p1 = &a; //포인터형 변수 p1에 'a'변수의 주소값을 구해 넣겠다.
        //'=': 대입 연산자, '&': 'address of 뒤에꺼'

*p1; //p1이 가리키는 곳에 가보자. 즉 19에 접근.
     //여기서 쓰인 '*' 연산자가 바로, 역참조 연산자!(가보자 연산자)

*p1 = 17; //p1에 있는 주소값이 가리키는 곳의 값을 바꿔보자.
          //(가보자)p1 (담자'=') 17;
printf("%d", a); // 17을 출력

 

자, 여기까지 포인터의 개념에 대한 이해를 돕기 위한 글이었습니다. 부디, 직접 이것 저것 실행해 보시고, 충분히 이해하시기를 바랍니다. 이 포인터를 활용한 다양한 작업이 등장하거나 필요할 때에도 당황하지 않으시길 기대합니다. 감사합니다.

 

desk

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s