Taking baby-developer steps

9. 포인터 - 포인터의 개념 / 포인터 관련 연산자(주소연산자, 포인터, 간접 참조 연산자) / 포인터의 기능 본문

CS 지식/C언어_basic

9. 포인터 - 포인터의 개념 / 포인터 관련 연산자(주소연산자, 포인터, 간접 참조 연산자) / 포인터의 기능

Surin Lee 2021. 3. 24. 23:52

포인터

향후 고급 프로그래머가 되기 위해선, C언어의 포인터를 이해하고, 더 하위 레벨의 언어라고 할 수 있는 어셈블리어 또한 접하게 될것이다. 일반적인 웹 및 앱 개발을 할 땐 몰라도 문제는 없으나, 코어 모듈 개발을 할 필요가 있다면 반드시 잘 알고 넘어가야 할 부분이다.

 

포인터의 개념

지금까지 변수는 그 자체로 자신의 자료형에 맞는 값을 저장했다. (ex> int, double) 포인터(Pointer) 변수는 특이한 변수로, 메모리 주소를 저장한다.(메모리에 주소 값 자체를 담는다.) 또, 단순히 주소 값만 저장하는게 아니라, 어떤 자료형의 주소 값인지를 함께 저장한다.

어떤 int형 변수를 만들었다고 하면, 이 int형 변수는 컴퓨터 메모리 공간 어딘가에 저장이 된다. 이 때, 그러한 변수의 위치 값을 가리키고 있는 int형 포인터가 존재할 수 있다.(*로 표시한다.)

 

  • 포인터는 특정한 변수 자체가 존재하는 메모리 주소의 값을 가진다. 즉, 컴퓨터 메모리에 바로 접근 할 수 있게 한다.

기존에는 변수 a를 이용해 5라는 값을 담아서 처리를 했었는데, 포인터b(*b)가 a의 주소, 즉 이  a 변수를 가리키도록 만들어서도 "5"라는 값에 접근 할 수가 있다.

또, 이 *b도 "변수"이기 때문에, 특정한 주소 값을 가진다. 즉, a와 b 모두 각기 다른 주소 값을 가진 변수 라고 할 수 있다. 다만 b는 포인터 변수이기 때문에, a라는 변수의 '주소'를 그 값으로 가지고 있다.

 

int *b = &a ;

위에서 처럼, '선언할 때' 쓰는 '*'는 포인터 변수임을 알려주기 위한 목적을 가진다. 

이후에 *b라고 쓰게 되면, 이것은 포인터 변수 b가 가리키는 주소의 값(위의 경우 a의 값인 '5')을 의미한다. 이런 맥락에서 이 '*'을 '간접 참조 연산자'라고 부른다.

 

포인터 관련 연산자

주소 연산자(&)

변수 앞에 붙어서 변수의 메모리 시작 주소 값(흔히, 포인터 변수의 '값'으로 들어갈 수 있는 요소)을 구한다.

ex) scanf()에 &a와 같은 형식으로 넣어주는 이유 : 특정한 변수가 메모리 내에 존재하는 주소 값 자체를 확인(주소를 가져온다)

포인터(*)

포인터 변수를 선언할 때 사용한다.

간접 참조 연산자(*)

선언된 포인터 변수가 가리키는 변수(값)를 구한다. (선언 이후에 사용하게 되면 포인터 변수가 '가리키는 변수의 값'을 가지게 된다.)

 

따라서, 선언 할 때와 선언 이후의 포인터 연산자(*)는 생긴 것만 같지, 기능은 다르다.

 

포인터의 개념

실제로 int a = 5;와 같이 변수를 할당하면, 메모리 주소상에서는 다음과 같이 기록된다. int형은 4B를 차지하므로, 메모리 주소를 1B 씩 표현 할 때 4칸을 차지한다.

 

포인터의 강력한 기능

  • 포인터는 컴퓨터 시스템의 특정한 메모리에 바로 접근 할 수 있다.

따라서 기존에 존재하던 중요한 메모리 영역에 접근하지 않도록 해야 한다. 따라서 다음과 같은 코드는 굉장히 위험한 코드이다.

 

int *a = 0x33484735;
*a = 0;

'0x33484735'라는 이 주소가 정확히 어떤 역할을 하는 건지, 확실하게 이해할 수 없기 때문에, 자신이 컨트롤 할 수 있는 범위가 아닌 이상 특정한 주소 값에 함부로 접근해서 다른 값으로 덮어 쓰면 안된다.

 

 

  • 포인터는 다중으로 이용할 수 있다.

즉, 포인터의 포인터의 포인터의 포인터를 만드는 것도 가능하다. 이와 같은 방법은 후에 게임 등을 개발 할 때, 난독화 기법으로도 사용 할 수 있다.

#include <stdio.h>

int main(void){
	int a =5;
    int *b = &a;
    int **c = &b;
    printf("%d\n", **c);
	return 0;
}

 

  • 배열과 포인터는 사실 동일하다

배열과 포인터는 서로 상호 치환되어서 사용될 수 있다. 배열을 선언한 이후에는 그 이름 자체가 포인터 변수와 동일하다.(그 이름 자체를 포인터 변수처럼 쓸 수 있다.)

#include <stdio.h>

int main(void){
	int a[] = { 1, 2, 3, 4, 5, 6};
    int *b = a;
    printf("%d\n", b[2]);
	return 0 ;
}

이때, 포인터 b를 선언할 때 a 앞에 주소 연산자가 없는 것을 볼 수 있다. 배열의 이름 자체를 주소 값으로 사용하고 있기 때문인데, 내부적으로 '배열의 이름 자체'는 '주소 값 자체'를 가지고 있기 때문에, 주소 연산자를 안 붙이고 사용할 수 있는 것이다. 후에 공부할 내용이지만, 배열의 이름 자체는 배열의 첫번째 원소의 주소 값과 같다(a = &a[0])

 

 

 

 

Comments