C 포인터 이해와 활용 (1)

이동욱

2021/06/26

Categories: 프로그래밍

8994506799_1

시작하기


포인터와 메모리


컴파일된 C 프로그램은 다음 세가지 종류의 메모리를 사용한다.

정적으로 선언된 변수들은 정적/전역 메모리에 할당된다. 또한 전역 변수들 또한 같은 메모리 공간에 할당된다. 정적/전역 변수들은 프로그램이 시작될 때 할다오디며, 프로그램이 종료될 때까지 메모리 공간에 남아 있다. 모든 함수에서 접근할 수 있는 전역 변수와는 달리 정적 변수의 접근 범위는 해당 변수를 선언한 함수로 제한된다

자동 변수는 함수 안에서 선언되고 함수가 호출될 때 생성된다. 자동 변수의 접근 범위는 선언된 함수로 제한되며, 함수가 호출되는 동안에만 존재한다. 일반적으로 블록문 안에서 선언된 변수의 범위는 해당 블록으로 제한한다. 이들은 종종 자동 변수라고 언급된다.

동적 (Dynamic) 메모리는 힙(Heap) 메모리 영역에 할당되고 필요한 경우에 해제된다. 포인터를 사용하여 할당된 메모리 영역을 참조하며, 포인터에 의해 접근이 제한 된다. 메모리를 해제하지 않는 한 메모리레 존재한다.

포인터를 잘 알아야하는 이유

포인터를 잘 사용하면 강력한 도구이지만, 포인터를 이용할 때는 다음과 같은 다양한 문제가 발생할 수 있다.

포인터 선언하기

int num;
int *pi;

주소 연산자

주소 연산자(&)는 변수의 주소를 반환하다. 아래와 같이 주소 연산자를 사용하여 pi 포인터를 num 변수의 주소로 초기화 할 수 있다.

num = 0
pi = #

가상 메모리와 포인터

간접지정 연산자로 포인터 역참조하기

int num = 5;
int *pi = #


printf("%d\n", *pi); // 5
*pi = 200;
printf("%d\n", num);

함수 포인터

void (*foo)();

NULL의 개념

- 널 개념
- 널 포인터 상수
- NULL 매크로
- NUL 아스키 문자
- 널 문자열
- 널 문장
#define NULL ((void*) 0)
pi = NULL;
널 포인터와 초기화되지 않은 포인터는 명백히 다르다. NULL을 포함한 포인터가 메모리상의 어떤 위치도 참조하지 않지만, 초기화되지 않은 포인터는 어떤 값이라도 포함될 수 있으며 참조될 수 있다.

포인터는 논리식에서 단독으로 피연산자로 사용될 수 있다. 예를 들어서, 아래 코드처럼 포인터가 NULL로 설정되었는지 테스트 할 수 있다.

if (pi) {
  // 널이 아닌 경우
} else {
  // 널인 경우
}

널 포인터가 포함한 주소는 유효한 주소가 아니므로 절대로 역참조 해서는 안되며, 널 포인터에 대한 역참조는 프로그램에 대한 비정상 종료를 초래한다.

void 포인터

void 포인터는 어떤 타입의 데이터도 참조할 수 있는 범용 포인터이다.

void *pv;

void 포인터 선어에는 두 가지 흥미로운 것이 있다.

int num;
int *pi = #
printf("Value of pi: %p\n", pi); // Value of pi: 0x7ffee6dfd9cc
void* pv = pi;
pi = (int*) pv;
printf("Value of pi: %p\n", pi); // Value of pi: 0x7ffee6dfd9cc
void 포인터 사용시 주의가 필요하다.
임의의 포인터를 void 포인터로 캐싱팅 한 후에, 기존 타입이 아닌 전혀 다른 타입의 포인터로 캐스팅하더라도 이를 막을 방법이 없다.
size_t size = sizeof(void*); // valid
size_t size = sizeof(void); // invalid

전역 포인터와 정적 포인터

int *globalpi;

void foo() {
  static int *staticpi;
}

int main() {
  ...
}

포인터의 크기와 데이터 타입

메모리 모델

I In L Ln LL LLn P Pn

Screen Shot 2021-06-26 at 10 06 25 PM

사전 정의된 포인터 관련 데이터 타입

size_t 타입의 이해

문자의 수나 배열 인덱스와 같은 크기 변수를 선언할 때는
size_t 타입을 사용하는 것이 좋다.
size_t 타입은 반복문이나 카운터나 배열의 참조 그리고 때로는 포인터 연산에서 사용될 수 있다.

intptr_t, uintptr_t 사용하기

상수와 포인터

상수에 대한 포인터

int num = 0;
const int limit = 500;
int *pi;
const int *pci;

pi = #
pci = &limit;
printf("%d\n", *pci);
pci = #

상수 정수를 가리키는 pci 포인터 변수의 선언은 다음을 의미한다.

비상수를 가리키는 상수 포인터

int num;
int *const cpi = #

위 선언은 다음을 의미한다.

상수를 가리키는 상수 포인터

상수를 가리키는 상수 포인터는 거의 사용되지 않는다. 포인터 값은 변경될 수 없으며, 포인터가 가리키는 값 역시 포인터를 통해 변경될 수 없다.

const int* const cpic = &limit;

다중 수준 상수 포인터

상수 포인터는 다중 수준의 간접 지정이 가능하다.

const int * const cpci = &limit;
const int * const * pcpci;
포인터의 종류 포인터 데이터 변경 포인터 대상 데이터 변경
비상수를 가리키는 포인터 O O
상수를 가리키는 포인터 O X
비상수를 가리키는 상수 포인터 X O
상수를 가리키는 상수 포인터 X X

참고 문헌

>> Home