int num1 = 10;
위와 같은 변수는 컴퓨터 메모리에 생성된다. 즉 메모리에 일정한 공간을 확보해두고 원하는 값을 저장하거나 가져오는 방식이다.
보통 변수는 num1과 같이 이름으로 사용하지만 메모리의 특정 장소에 있으므로 메모리 주소로도 표현할 수 있다.
일상생활에서 집을 구분할때 주소를 사용하는 것과 같은 원리이다.
변수의 메모리주소를 구할때는
- &(num1)
이런식으로 변수 앞에 &(주소 연산자)를 붙여준다.
메모리 주소는 printf에서 서식 지정자 %p를 사용한다.
포인터 변수 선언하기
변수의 메모리 주소를 구했는데 메모리 주소는 어디에 저장해야 할까?
C언어에서 메모리 주소는 포인터(pointer) 변수에 저장한다.
포인터 변수는 *를 사용하여 선언한다.
(포인터 변수는 줄여서 포인터로 부르기도 한다.)
- 자료형 *포인터이름
- 포인터 = &변수
위에서 알 수 있듯, 메모리 주소와 포인터는 같은 의미이다.
포인터 변수를 선언할 때에는 자료형을 먼저 알려준 뒤 *를 붙이는 방식을 사용한다.
변수의 자료형이 int이면 이 변수의 메모리 주소를 저장하는 포인터는 int * 이다.
여기서 int *은 영어로 pointer to int로 읽는데, int형 공간을 가리키는 포인터 라는 뜻이다.
(간단하게 int 포인터라고도 한다)
int *numPtr; // 포인터 변수 선언
int num1 = 10;
numPtr = &num1; // num1의 주소를 포인터 변수에 할당
numPtr은 10이 저장된 메모리 공간을 가리킨다. 즉 변수 num1이 있는 공간을 가리키게 된다.
역참조 연산자 사용하기
포인터 변수에는 메모리 주소가 저장되어 있다. 이때 메모리 주소가 있는 곳으로 이동해서
값을 가져오고 싶다면 역참조(dereference) 연산자 *를 사용한다.
- *포인터
포인터는 변수의 주소만 가리키며, 역참조는 주소에 있는 값에 접근한다.
이번에는 포인터 변수에 역참조 연산자를 사용한 뒤 값을 할당해보자.
- *포인터 = 값;
- 변수는 메모리 주소를 몰라도 값을 가져오거나 저장할 수 있다.
- 구조 연산자(&)는 변수의 메모리 주소를 구한다. (메모리 공간이 어디에 있는지 위치만 안다)
- 역참조 연산자(*)는 메모리에 저장된 값에 접근할 수 있다 즉, 메모리 주소에 접근하여 값을 가져오고 저장한다.
- 포인터는 변수의 메모리 주소만 가리킨다. (메모리 공간이 어디에 있는지 위치만 안다)
다양한 자료형의 포인터 선언하기
#include <stdio.h>
int main()
{
long long* numPtr1; // numPtr1 포인터 변수 선언.
float* numPtr2; // numPtr2 포인터 변수 선언.
char* cPtr1; // cPtr1 포인터 변수 선언.
long long num1 = 10;
float num2 = 3.5f;
char c1 = 'a';
numPtr1 = &num1; // numPtr1에 num1 메모리 주소 할당
numPtr2 = &num2; // numPtr2에 num2 메모리 주소 할당
cPtr1 = &c1; // cPtr1에 c1 메모리 주소 할당
printf("%lld\n", *numPtr1); // lld 서식지정자에 numPtr1 역참조 후 메모리 값 출력
printf("%f\n", *numPtr2); // f 지정서식자에 numPtr2 역참조 후 메모리 값 출력
printf("%c\n", *cPtr1); // c 지정서식자에 cPtr1 역참조 후 메모리 값 출력
return 0;
}
C언어에서는 사용할 수 있는 모든 자료형을 포인터로 만들 수 있다.
그런데 그냥 포인터 자료형이라고 따로 만들면 간단할 텐데 왜 자료형마다 포인터를 선언하도록 만들었을까?
포인터에 저장되는 메모리 주솟값은 정수형으로 같지만, 선언하는 자료형에 따라 메모리에 접근하는 방법이 달라진다.
즉 위와 같이 포인터를 역참조하면 선언한 자료형의 크기에 맞춰서 값을 가져오게 된다.
- long long는 8바이트이니 8바이트에 정수 10이 저장됨.
- float는 4바이트이니 4바이트에 실수 3.5가 저장됨.
- char는 1바이트이니 1바이트에 문자 a가 저장됨.
void 포인터 선언하기
위의 long long *numPtr1; 이나 float *numPtr2;는 자료형이 정해져있으나,
C언어에서는 자료형이 정해지지 않은 포인터가 있다.
void 포인터라는 포인터인데 다음과 같이 void 키워드와 *로 선언한다.
- void* 포인터이름;
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num1 = 10; // 변수 num1에 10 할당
char c1 = 'a'; // 변수 c1에 'a' 할당
int* numPtr1 = &num1; // numPtr1에 num1 메모리 주소 할당
char* cPtr1 = &c1; // cPtr1에 c1 메모리 주소 할당
void* ptr; // void 포인터 선언
// 포인터 자료형이 달라도 컴파일 경고가 발생하지 않음.
ptr = numPtr1;
ptr = cPtr1;
// 포인터 자료형이 달라도 컴파일 경고가 발생하지 않음.
numPtr1 = ptr;
cPtr1 = ptr;
return 0;
}
void 포인터는 자료형이 정해지지 않은 특성 때문에 어떤 자료형으로 된 포인터든 모두 저장이 가능하다.
반대로 다양한 자료형으로 된 포인터에도 void 포인터를 저장할 수 있다.
이런 특성 때문에 void 포인터는 범용 포인터라고 불린다.
단, void 포인터는자료형이 정해지지 않았으므로 값을 가져오거나 저장할 크기도 정해지지 않는다.
void 포인터는 함수에서 다양한 자료형을 받아들일때, 함수의 반환 포인터를 다양한 자료형으로 된 포인터에 저장할 때, 자료형을 숨기고싶을 때 사용한다.
이중 포인터 사용하기
지금까지 변수의 포인터를 선언했다면
포인터의 포인터도 가능하지 않을까?
이번에는 포인터의 메모리 주소를 저장하는 포인터의 포인터를 선언해보자.
포인터를 선언할 때 *를 두번 사용하면 포인터의 포인터(이중 포인터)를 선언한다.
- 자료형** 포인터이름;
포인터도 실제로는 변수이기 때문에 메모리 주소를 구할 수 있다.
하지만 포인터의 메모리 주소는 일반 포인터에 저장할 수 없고, int **numPtr2; 처럼 이중 포인터에 저장해야 한다.
int **numPtr2를 영어로 읽으면 pointer to pointer to int가 된다.
(numPtr2 → numPtr1 → num1)
포인터를 선언할 때 *의 개수에 따라서 삼중 포인터, 사중 포인터, n중 포인터를 만들 수 있다.
마찬가지로 역참조도 그만큼 사용할 수 있다.
잘못된 포인터 사용
#include <stdio.h>
int main()
{
int* numPtr = 0x100;
printf("%d\n", *numPtr);
return 0;
}
위와 같은 경우, 값이 제대로 출력되지 않는다.
0x100이 실제 메모리 주소가 아니라서 그렇다.
이 경우, 포인터 변수에 저장할 때 실제 메모리 주소를 가져다주면 거기에 포인터 지정이 된다.
심사문제: 포인터와 주소 연산자 사용하기
정답을 보려면 더보기를 클릭하세요.
'C, C++' 카테고리의 다른 글
C언어 코딩도장(배열 사용하기) / 2진수 계산기 (0) | 2022.04.29 |
---|---|
C언어 코딩도장(메모리 사용하기) (0) | 2022.04.29 |
[C언어] 약수 구하기 (0) | 2022.04.28 |
[C언어] FizzBuzz / 삼항연산자 중첩 (2) | 2022.04.28 |
C언어 코딩도장(goto 사용하기) (0) | 2022.04.28 |