본문으로 바로가기

[ 8. C언어의 기본 - 포인터 ]

category Algorithm/C언어 2021. 10. 11. 15:08

우리는 앞에 포스팅에서 어떤 수나 문자를 저장하기 위해 변수를 사용했다.

사실 이 변수는 기억장소의 어느 위치에 대한 이름이며, 위치는 주소로도 표현할 수 있다.

예시로 나는 친구들과 만나기 위해 " OO 카페 앞으로 와"라고 말하기도 하지만

OO카페가 한 두개가 아니고 차로 이동하는 친구라면 편하게 네비 주소로

OO카페의 주소인 " OO시 OO구 OO동 OO번지로 와"라고 말할 수 있다.

 

C언어에서는 이처럼 변수의 위치, 즉 주소를 제어할 수 있는 기능을 제공한다.

변수의 주소를 출력할 수도 있고 주소를 주소 변수에 저장할 수도 있다.

주소를 저장할 수 있는 변수를 포인터 변수라고 한다.

 

일단 먼저 감을 먼저 잡도록 하자.

1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main(){
 
  char language = 'c';
  int thisyear = 2021;
  double p1 = 3.14;
}
 
cs
ㅇㅇㅇㅇㅇㅇㅇ

문자 변수 language, 정수 변수 thisYear, dobule 변수 p1를 지정하고 값을 입력하였다.

이렇게 변수에 입력된 값들은 메모리의 어딘가에 저장된다.

 

메모리를 요리 재료와 도구들을 얹는 조리대라고 비유할 수 있는데,

이렇게 한정된 공간 안에서 변수나 함수 등의 값들이 필요에 따라 올려놓아지고,

다 쓰이면 치워지고 하면서 프로그램이 돌아가게 된다. 

 

그런데 프로그램을 짜다보면 어느 변수에 지정한 값을 다른 여러 곳에서 써야 할 때가 있다.

 

팀 프로젝트를 하면서 필요한 내용을 팀원들에게 전달을 해주어야 할 때가 있다.

한명만 있으면 상관없지만 팀원들에게 전부 다 공유해야 한다고 했을 때

일일이 내용을 직접 설명해줄 필요없이 자료를 공유해 몇 페이지에 있는지만 말해주면 된다.

 

C에서 어느 함수에 변수가 인자로 주어질 때

Ex) int argument = 100;

    function_1(argument);

 

그 값이 복사되어서, 즉 배껴 적어져서 넘어가게 된다.

 

복사가 된다는 건 메모리의 다른 어딘가의 그 크기만큼을 중복된 값이 차지한다는 얘기다.

그렇게 메모리를 차지않고 적어서 보내줄 수 있도록 하는게 바로 포인터이다.

메모리의 모든 칸에는 각각의 주소가 존재한다.

 

예제를 살펴보도록 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
 
int main(){
 
 
  int x = 1;
  int y = 200;
  int z = 20211011;
 
  int *p1 = &x;
  printf("p1 Points %d now. \n"*p1);
 
  x ++;
  printf("p1 Points %d now. \n"*p1);
 
  p1++;
  printf("p1 Points %d now. \n"*p1);
 
  p1--;
  printf("p1 Points %d now. \n"*p1);
}
 
cs

위 코드는 x를 바라보도록 한 p1이 가리키는 값을 출력해 보는 것이다.

x에 넣은 대로 1이 나오고, x의 값이 증가시키면 예상대로 2가 나온다.

하지만 포인터의 값을 증가시키면 어떻게 될까?

포인터의 정수값을 더하면 자료형의 크기 곱하기 그 값만큼 오른쪽으로 이동하게 된다.

x 다음에 y를 지정했으니까, 포인터를 1을 더해서 오른쪽으로 옮기면 y값이 출력되지 않을까?

하지만 결과를 보면 웬걸.. 4195760 이라는 엉뚱한 값이 나와버렸다. 이렇게 우리의 기대와는 달리, 변수들은 프로그래머가 지정하는대로 예측가능한 곳에 나란히 배열되지 않는다..애먼 포인터만 메모리란 우주에서 미아가 되어버렸다.포인터를 찾아주기 위해 p1--; 실행하면 원래 자리로 돌아오는 것도 확인이 가능하다. 

 

그럼! 값들이 나란히 연속으로 배열되는 경우는 어느 때일까?

정답은 배열을 사용했을 때이다.

 

다음 포스팅에선 배열을 배워볼 것이다.포인터는 배열과 연계되어서 사용할 수 있다는점을 기억해두자.

 

 

포인터

 

- 포인터 변수를 선언할 때는 자료의 형을 먼저 쓰고 변수명 앞에 *를 붙인다.

 

1) 포인터 연산자

  - 주소 연산자(&)

  - 참조 연산자(*)

 

1-1) 주소 연산자

- 주소 연산자는 변수의 이름 앞에 사용하여, 해당 변수의 주소값을 반환한다.

- 포인터 변수에 주소를 저장하기 위해 변수의 주소를 구할 때는 변수 앞에 &를 붙인다.

- "&" 기호는 앰퍼샌드(ampersand)라고 읽으며, 번지 연산자라고도 불린다.

 

1-2) 참조 연산자

- 실행문에서 포인터 변수에 *를 붙이면 해당 포인터 변수가 가리키는 곳의 값을 말한다.

 

예를 들어, a 변수에 1000을 저장시키고, a 변수의 주소를 포인트 변수 b에 기억시켰다면

다음과 같이 설명할 수 있다.

 

- a는 메모리의 4번지에 대한 이름이다.

- a 변수의 주소는 4이다.

- a 변수에는 1000이 기억되어 있다.

- 4번지에는 1000이 기억되어 있다.

- &a는 a 변수의 주소를 말한다. 즉, &a는 4이다.

- 포인트 변수 b는 a 변수의 주소를 기억하고 있다.

- 포인터 변수가 가리키는 곳의 값을 말할 때는 *를 붙인다.

- *b는 b에 저장된 주소가 가리키는 곳에 저장된 값을 말하므로 1000 이다.

 

2) 포인터의 선언

<문법>

타입* 포인터이름;

 

- 타입이란 포인터가 가리키고자 하는 변수의 타입을 명시한다.

- 포인터 이름은 포인터가 선언된 후에 포인터에 접근하기 위해 사용된다.

 

- 포인터를 선언한 후 참조 연산자(*)를 사용하기 전에 포인터는 반드시 먼저 초기화가 되야한다.

( 그렇지 않으면 의도하지 않은 메모리의 값을 변경하게 되기 때문)

따라서 초기화하지 않은 포인터에 참조 연산자를 사용하면 오류를 발생시킨다.

 

따라서 다음과 같이 포인터의 선언과 동시에 초기화를 함께 하는 것이 좋다.

 

<문법>

타입* 포인터이름 = &변수이름;

또는

타입* 포인터이름 = 주소값;

 

 

포인터 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
 
int main(){
 
 
  int a = 10;
 
  int *p1 = &a;
  
  printf(" a의 주소값 = %x \n", p1);
  printf(" a의 값 = %d"*p1);
}
 
cs

p1는 a의 주소값, *p1은 a의 값을 가지고 있는 것이다.

& 연산자는 변수의 주소 값을 반환하므로 상수가 아닌 변수가 피연산자이어야 한다.

& 연산자의 반환값은 포인터 변수에 저장한다.

 

-참조에 의한 호출 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
 
int main(){
 
  int num1 = 100, num2 = 100;
  int *pnum;
 
   pnum = &num1;
  *pnum += 30;
 
  pnum = &num2;
  *pnum -= 30;
 
  printf(" num1 :%d, num2 :%d \n", num1, num2);
}

결괏값 : num1 : 130, num2 : 70
 
cs

pnum의 참조대상을 num1로 하여 30을 증가,

           참조대상을 num2로 하여 30을 감소

 

이것으로 간단히 마무리하고 배열을 배울 때 포인터를 활용하여 해보겠다.