포인터(pointer) 개념 이해

April·2021년 10월 8일
2

🌹CS

목록 보기
14/17
post-thumbnail

C언어로 포인터의 기본 개념 이해하기!

포인터(pointer)는 프로그래밍 언어에서 변수의 메모리 공간의 주소를 가리키는 변수를 말한다. 포인터가 가리키는 값을 가져오는 것을 역참조라고 한다.

일반적으로 포인터는 메모리 주소로 바꿀 수 있다. 포인터는 다른 변수나 함수를 가리키도록 사용된다.


C 언어의 포인터

  • 모든 변수는 메모리에 값을 저장
  • const와 같은 고정값 변수 외의 모든 변수는 메모리 중에 RAM에 할당
  • 이러한 메모리의 공간을 구별하는 것이 메모리 주소값이다.
  • 포인터는 연산이 가능하다


:: 포인터 할당 및 해제

C언어에서 변수는 정적변수동적변수가 있다. 처리할 데이터의 숫자를 예측할 수 있다면 정적으로 선언하면 된다. 예시) 배열변수

그러나 데이터 처리를 미리 예측할 수 없다면 최대의 데이터 처리량을 정하고 정적으로 선언할 수 밖에 없다. 이 문제를 해결하기 위해 동적 변수를 사용한다.

C에서는 전통적으로 동적할당 변수를 잡을 경우 malloc() 함수를 사용하여 데이터 저장공간을 확보한다.

  • 할당 malloc(): 힙영역으로부터 데이터 공간을 할당 받는다.
  • 재할당 realloc(): 이미 할당된 메모리 공간의 크기를 조정한다.
  • 해제 free(): 더 이상 쓸 필요가 없는 메모리 공간을 힙영역에 반환한다




:: 운영 체제에서 할당하는 동적 공간

프로세서의 힙공간은 제한적이다.

MFC(마이크로소프트 파운데이션 클래스 라이브러리(Microsoft Foundation Class Library)는 C++용 프로그램 라이브러리)와 같은 윈도 프로그램의 경우, 필요에 따라 운영 체제의 자원을 할당 받아 동적공간을 확보할 수 있다

이것 역시 동적 할당이기 때문에 포인터 변수를 사용하여 처리한다.

프로세서에서 할당 받은 공간은 실행이 끝날 때까지 동적으로 유지되지만, 이와 관련된 할당은 운영 체제와 관련 되어 있으므로 메모리 공간을 잠금을 할 경우도 있다.

  • 할당: 운영 체제로부터 메모리 공간 일부를 빌린다. 정적 메모리 할당과 동적 메모리 할당의 2가지 종류가 있다.
  • 재할당: 이미 할당된 메모리 공간의 크기를 조정한다.
  • 해제: 더 이상 쓸 필요가 없는 메모리 공간을 운영 체제에 반환한다.

이 작업이 제대로 이루어지지 않을 경우 운영체제는 리소스 부족, 알 수 없는 오류, 잘못된 연산 수행과 같은 오류를 일으킨다




✔️ 포인터 변수 선언

int a = 5;
int *b = &a;
  • int *b = &a; 처럼 선언할 때에는 변수명 앞에 *를 붙혀서 포인터 변수임을 명시

  • 선언한 이후에 *b라고 쓰게 되면, 포인터 변수 b가 가리키는 주소의 값을 의미. 즉 5가 된다

  • &a는 변수 a&를 붙힘으로 a의 주소값을 의미

이름설명
주소 연산자(&)변수 앞에 붙어서 변수의 메모리 시작 주소 값을 구함
포인터(*)포인터 변수를 선언할 때 사용
간접 참조 연산자(*)선언된 포인터 변수가 가리키는 변수를 구함

예제 코드

#include <stdio.h>

int main(void) {
	int a = 5;
	int *b = &a;         // (1)
	printf("%d\n", *b);  // (2)
    system("pause");
    return 0;
}

(1) 이때의 *b*포인터. 포인터 변수 b가 a를 가리키고 있다
(2) 이때의 *b*간접 참조 연산자. 포인터 변수 b가 가리키고 있는 변수 값을 가리키고 있다. 즉, 5


✔️ 다중 포인터

다중 포인터의 개념도 있다. 예시) 포인터의 포인터

int *a = 5;
int *b = &a;
int **c = &b;




:: 포인터 배열

✔️ 포인터 배열의 구조 분석

  • 배열은 포인터와 동일한 방식으로 동작한다
  • 배열의 이름은 배열의 원소의 첫 번째 주소가 된다
  • 유일한 차이점은, 포인터는 변수이며 배열의 이름은 상수이다
    • 즉, 포인터는 변수는 라서 바뀔 수 있지만
    • 배열의 이름은 상수라서 바뀔 수 없다

1)예제 코드

#include <stdio.h>

int main(void) {
	int a = 10;
	int b[10];   // (1)
	b = &a;      // (2)
    system("pause");
    return 0;
}

(1) 변수 b를 선언하고 10 크기의 배열이라고 지정
(2) 변수명 b는 상수이기 때문에 다른 주소값으로 변경할 수 없다. 즉 오류 발생.


2)예제 코드

하지만 반대로 포인터를 배열처럼 사용할 수도 있다

#include <stdio.h>

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

(1) b라는 포인터 변수를 선언하고 a를 가리키게 함
(2) b[2]를 출력. 즉 b가 가리키고 있는 a[2]를 출력하므로 결과는 3


3)예제 코드

배열의 이름은 배열의 원소의 첫 번째 주소가 된다

#include <stdio.h>

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

(1) 배열의 첫 번째 원소 값을 넣어도 동일하기 동작.
int *b = aint *b = &a[0] 는 같은 의미




:: 함수 포인터

  • C언어에서는 함수의 이름을 이용해 특정한 함수를 호출
  • 함수의 이름은 메모리 주소를 반환
    즉, 배열의 이름이 메모리 주소값을 반환하는 것과 마찬가지로 함수 또한 내부적으로 컴퓨터의 메모리에 정보가 저장된다는 것을 의미
#include <stdio.h>

void function() {
	printf("hello world");
}

int main(void) {
	printf("%d\n", function);  //  (1)
    system("pause");
    return 0;
}

(1) function이라는 함수의 메모리 주소값이 출력


  • 함수 포인터는 특정한 함수의 반환 자료형을 지정하는 방식으로 선언할 수 있다.

  • 함수 포인터를 이용하면 형태가 같은 서로 다른 기능의 함수를 선택적으로 사용할 수 있다.

    반환 자료형(*이름)(매개 변수) = 함수명;
#include <stdio.h>

void myFunction() {
	printf("hello");
}

void yourFunction() {
	printf("world");
}

int main(void) {
	void(*fp)() = myFunction;
    fp();
    fp() = yourFunction;
    system("pause");
    return 0;
}

두 개의 함수가 차례대로 실행되면서 hello world를 출력




✨ tl;dr

  • 포인터의 개념
    • 포인터는 특정한 변수 자체가 존재하는 메모리의 주소의 값을 가진다
    • 단순히 주소값만 저장하는게 아닌, 어떤 자료형인지도 포함하여 저장

  • 포인터의 강력한 기능
    • 포인터는 컴퓨터 시스템의 특정한 메모리에 바로 접근할 수 있다
    • 따라서 기존에 존재하던 중요한 메모리 영역에 접근하지 않도록 해야 한다
    • 다중 포인터의 개념도 있다. 예시) 포인터의 포인터
    • 배열과 포인터는 사실 내무적으로 거의 동일하다. 배열을 선언한 이후에는 그 이름 자체를 포인터 변수처럼 사용할 수 있다.
profile
🚀 내가 보려고 쓰는 기술블로그

0개의 댓글