여행을 개발하다

포인터(Pointer) 본문

BackEnd/C++

포인터(Pointer)

yhtragramming 2019. 5. 9. 16:03

안녕하세요~

 

드디어 C계열 언어의 지옥이라고 불리는

'포인터(Pointer)'에 대해 이야기 할 시간이 되었습니다.

 

많은 분들이 '멘붕'에 빠졌다고 말씀하셔서,

저 또한 많이 긴장하고 들었습니다....

 

하지만 어차피 부딪힐 난관!

초반에 개념만 잘 이해하고 나간다면, 극복 가능한 페이지라고 생각합니다.

그럼 지금부터 포인터(pointer)의 세계로 들어갑니다!


1. 포인터란?

- 값(상수)을 기억하지 않고 변수나 배열이 생성된 메모리의 주소를 기억하는 변수

 

변수를 선언할 때는 초기값을 반드시 지정해줘야 한다는 것. 다들 기억하시죠?

 

이와 동시 컴퓨터 내에서는 새로운 메모리가 형성이 되고, 변수는 이 메모리에 저장되는데요.

이 메모리의 주소를 알려주는 것이 바로 포인터의 기본이자 핵심 개념이라고 보시면 됩니다.

 

포인터는 변수의 값 대신, 변수가 저장된 주소를 가지고 있다는 점에서 또다른 '변수'가 됩니다.

여기서 포인터는 '포인터 변수'를 짧게 이르는 말입니다.

 

2. 포인터 선언 규칙과 특징

포인트를 선언할 때는, 다음과 같은 규칙을 따라야 합니다.

 

- 포인터를 선언할 때는 포인터임을 컴파일러에게 알려주기 위해 변수명 앞에 "*"를 붙여서 선언한다.

- 포인터를 선언할 때는 포인터에 저장될 주소에 선언된 기억 장소의 자료형을 붙여주고, 자료형에 관계없이 무조건 4바이트의 크기를 가진다.

 

일반 변수와 달리 포인터는 "자료형 *포인터명"으로, 반드시 '*'를 붙여서 선언해야 합니다.

 

부가적으로, 선언된 자료형이 int, double, long long int, float인가에 상관없이,

무조건 4바이트의 크기를 갖는다는 사실을 기억하셔야 합니다.

 

확인을 위해 예제를 살펴보겠습니다.

바이트 단위로 변수의 크기를 출력해주는 함수 'sizeof(variable)'를 통하여,

포인터의 크기를 출력해보겠습니다.

----------------------------------------------------------------------------------------------------

먼저, 5개의 포인터 변수(i, j, k, l, m)를 선언하되, 자료형을 모두 다르게 하겠습니다.

 

int *i = NULL;

long *j = NULL;

long long int *k = NULL;

double *l = NULL;

float *m = NULL;

 

그 다음에는,

"======================================================="

선으로 구분하여, 정수형, 실수형 변수의 자료형을 각각 다르게 하여 변수(n,o,p,q,r)를 선언하고,

그 크기를 출력해보겠습니다.

그 결과, 일반 변수는 자료형의 크기에 맞게 출력하는 반면,

포인터는 자료형의 종류에 상관없이 4바이트의 크기를 가진다는 것을 확인할 수 있었습니다.

 

3. 포인터 초기화

- 포인터는 일반 변수와 같이 선언시 초기화를 할 수 있고, 선언 후 주소를 넣어줄 수 있다.

- 포인터 선언시 초기치를 NULL(혹은 00000000)로 주면 빈 포인터가 만들어진다.

 

빈 포인터의 선언은 위와 같은 2가지의 방법으로 가능하며, 결과치는 두 개가 동일합니다.

4. 포인터의 연산

포인터의 연산자를 크게 두 개로 나누어서 살펴보겠습니다.

 

① &

- 이항 연산자로 사용되면 bit 간의 and 연산을 하는 비트 연산자로 사용된다.

- 단항 연산자로 사용되면 기억 장소가 메모리에 생성된 주소를 얻어오는 번지 연산자로 사용된다.

 

② *

- 이항 연산자로 사용되면 숫자의 곱셈을 실행한다.

- 단항 연산자로 사용되면 포인터에 저장된 주소가 참조하는 값을 얻어오는 참조 연산자로 사용된다.

그럼 포인터의 연산이 실제로 어떻게 이루어지는지 예제를 통해 살펴보겠습니다.

 

정수형 변수인 num1, num2, 정수형 포인터 p를 선언하겠습니다.

 

num1은 정수 300을 넣어주고,

p에는 num1, 즉 정수 300이 저장되어 있는 주소값을 저장하고,

num2에는 주소값 p에 저장되어 있는 값을 저장시키겠습니다.

 

초기화가 완료된 후, num1, p, num2를 출력해보니, 다음과 같은 결과가 나왔네요.

 

위의 결과를 보면 가장 기초적인 개념이지만 매우 헷갈릴 수 있는 부분이 있는데요.

 

num1에 저장했던 300이라는 값은 우리가 매번 해오던 변수를 초기화한 것이고,

포인터 변수 p에는 300이라는 값이 저장된 주소값을 기억시켰으며,

다시 num2에서는 주소 p에 저장된 값을 불러온 결과값을 출력하고 있는 것입니다.

 

결론적으로, 300이라는 값은 '19921692'라는 주소를 갖고 있는 메모리에 저장이 된 것이겠죠?

 

5. 배열과 포인터

배열 또한 포인터형 변수로 선언하고, 사용할 수 있습니다.

 

특히, 배열의 크기를 정하고 선언하는 '정적 할당'을 넘어, 배열의 크기가 어떻게 될지 모르는 '동적 할당'의 경우 반드시 포인터가 필요합니다.

 

이 때, 포인터에는 배열의 시작 위치가 저장됩니다

 

예제를 통해 살펴보겠습니다.

 

{1, 2, 3, 4, 5} 를 인수로 갖는 배열 data, 배열 원소의 인덱스 값을 저장하는 i,

data의 주소값을 기억할 포인터 변수 p를 선언합니다.

 

그리고 data 배열의 주소값을 p에 저장시키고, 배열속 원소들의 주소값을 차례대로 출력해보겠습니다.

출력 값을 보면, data[0]의 주소값이 포인터 변수 p에 저장된 것을 확인해 볼 수 있습니다.

 

data 배열의 인덱스가 1씩 증가할수록, 주소값이 4씩 증가한 이유는 배열이 4바이트의 크기를 갖는 정수들을 인수로 갖고 있기 때문이에요!

 

이처럼 연속적인 데이터들은 규칙적인 간격으로 메모리에 저장된다는 것을 알 수 있습니다.

 

 


포인터는 C계열 언어의 꽃이라고 할 수 있을 정도로 매우 중요합니다.

포인터를 통해 보다 정교한 메모리 할당과 프로그래밍이 가능하기 때문이에요.

 

하지만 메모리를 지정하여 사용할 수 있다는 점에서 보안상 취약점도 무시할 수 없었습니다.

그래서 Java와 같은 타 언어가 그것을 가지고 가지 않았다는 말도...

 

지금까지 포인터에 대해 알아보았습니다.

다음 시간에 봬용 : )

Comments