본문 바로가기
C C++/C언어 기초

C언어 포인터 이해의 시작 (포인터 정의, 포인터 선언, 포인터 저장 방식, 포인터 자료형, & 연산자, *연산자)

by Go! Jake 2022. 5. 1.

C언어 포인터는 '다른 변수의 메모리 주소 값'을 저장하는 변수를 의미합니다. C언어가 Low 레벨 언어라고 불리는 대표적인 이유가 포인터를 이용 해 메모리에 직접 접근이 가능하기 때문입니다. 포인터는 무엇이고 어떻게 쓰일 수 있는지에 대해 알아보도록 하겠습니다.

포인터 정의

앞 서 말씀드린 바와 같이 C언어 포인터는 '메모리 주소 값'을 저장하는 변수입니다. 이것이 어떤 의미일까요?

- 변수는 메모리 상에 존재합니다.

- 변수는 메모리 상에서 특정 주소값을 가지고 있습니다. 

우리가 흔히 선언하는 문자 또는 정수형도 주소 값을 가지고 있습니다. 각 IDE에서 아래를 실행 해 보시기 바랍니다.

#include <stdio.h>
#pragma warning(disable:4996)



int main(void)
{
    char ch = "A";
    int a = 3;

    printf("%p\n", &ch);
    printf("%p", &a);
}

출력:

0000006DA3D1FC44
0000006DA3D1FC64

 

제 PC에서는 위와 같은 주소값을 얻었습니다. 이것이 변수 ch와 a의 각각 주소값입니다. 

주소값
(1BYTE 단위)
0000006DA3D1FC44 ...... ...FC64 ...FC65 ...FC66 ...FC67
A (char ch = 'A') ...... 3 (int a = 3)

위는 각 변수의 1 BYTE 단위 주소를 표현하였을 때입니다. char 문자열은 1바이트이고, int 정수형은 4바이트이기 때문에 위와 같은 주소값이 쓰였습니다.

 

따라서,

- char형 변수 A 값은 0000006DA3D1FC44에 할당되어 있습니다.

- int형 변수 a 값은 0000006DA3D1FC64에서부터 0000006DA3D1FC67에 할당되어 있습니다.

 

우리는 이 때, 주소 값 저장하여 주소 값을 통해 변수에 접근하고 싶습니다. 이에 따라 응용할 수 있는 부분은 무궁무진합니다. 예를 들어 함수의 주소 값을 가리켜 함수를 사용할 수 있고, 변수의 주소 값을 가리켜 변수의 값을 변경할 수 있습니다.

 

이에 따라, 우리는 포인터 변수를 통해 위와 같이 주소 값을 저장할 것입니다.

 

포인터 변수 저장 방식

포인터 변수는 포인터 변수가 가리키는 변수의 주소값을 저장한다는 것을 알아보았습니다. 그럼 어떤 식으로 저장하게 될까요? 위에서 알아 본 정수형 값 3에 대해서는 다음과 같습니다.

주소값
(1BYTE 단위)
0000006DA3D1FC64
...FC65 ...FC66 ...FC67
3 (int a = 3)

포인터는 위에서 알아 본 int a = 3이라는 값을 가질 때 int a 변수의 첫 주소를 가리킵니다.

우선 아래 예시를 통해 알아보도록 하겠습니다. 포인터 선언은 뒤에서 다루니 예제에 집중 부탁드립니다.

#include <stdio.h>

int main(void)
{
	int a;
	int* address;

	address = &a;
	
	printf("int a의 주소 %p\n", &a);
	printf("int a를 가리키는 포인터 address 값 %p\n", address);

	return 0;
}

int a의 주소와 int a를 가리키는 포인터 address 값 모두 000000000062FE14라는 값이 출력되었습니다. (메모리 상황에 따라 주소 값을 실행 때마다 달라질 수 있습니다.)

 

여기서 &a는 a의 주소를 가리키는 연산자입니다.

address = &a;를 통해 a의 주소값이 address라는 포인터에 담기게 되었습니다.

 

int a의 주소인 &a와 이를 가리키는 포인터 address 값이 같다는 것을 확인하였습니다. 즉, 다른 변수의 메모리 주소 값을 저장하고 있습니다.

포인터 변수 선언 및 자료형

포인터 변수 선언은 가리키는 변수의 자료형에 따라 선언하면 된다. 별표만 붙이면 된다.

#include <stdio.h>

int main(void)
{
	int* address;
	return 0;
}

address 변수 선언 시 int* address로 'int형 포인터 변수인 address'를 선언하였습니다. 이 포인터로 int형의 주소값을 담을 수 있습니다.

int* address, int * address, int *address 등 별표의 위치를 옮겨서 선언할 수 있습니다. 다만 가독성을 위해 int*라고 선언하는 편입니다.

 

마찬가지로 아래와 같이 int*, double*, float* 등 각 포인터형을 선언할 수 있습니다.

#include <stdio.h>

int main(void)
{
	int* address;
	double* address_2;
	float* address_3;
	return 0;
}

이와 같이 포인터 선언에서 자료형은 '포인터 형'으로 불립니다.

int*:  int형 포인터

int* address: int형 포인터, 'address'

 

가리키는 변수에 대해 포인터로 접근할 때 정확한 자료형으로 주소값에 저장된 값을 읽어와야 하므로 포인터 형도 변수형 형태와 동일하게 맞춥니다.

 

&, * 연산자와 포인터 변수의 이해

결론적으로 * 연산자는 포인터에서 중요한 2가지 역할을 한다.

1) 포인터를 선언하는 경우

2) 포인터 변수에 저장된 메모리를 참조하는 경우

 

1번은 포인터 선언에서 이미 설명되었다.

2번의 저장된 메모리를 참조한다는 것은 무슨 의미일까? 바로 해당 변수 값을 직접 참조한다는 의미이다.

#include <stdio.h>

int main(void)
{
	int* address;
	int a = 3;
	
	address = &a;
	printf("%d", *address);
	
	return 0;
}

해당 변수를 어떻게 직접 참조하는 것인지 설명드리겠습니다.

- address = &a;를 통해 포인터형 변수 address가 a 변수를 가리킵니다. 즉, a 변수의 주소값을 가지게 됩니다.

- printf("%d", *address);를 통해 출력을 해 보면, a 값인 3이 출력되었습니다. *address라는 표현으로, a 값 3을 출력한 것입니다.

- 즉, *address = a 표현과 같게 됩니다.

address가 가진 주소를 통해 a 값에 접근한 것입니다. 그 표현으로 *address입니다.

 

포인터에서 * 연산자는 이와 같이 포인터 변수 선언과, 포인터 변수에 저장된 메모리를 참조할 때 사용됩니다.

포인터 변수의 크기

포인터 변수는 기본적으로 '메모리 주소 값'을 저장하기 때문에 주소 값 저장에 필요한 크기를 가지면 됩니다.

포인터 변수 크기는 32 bit 시스템에서는 4byte, 64 bit 시스템에서는 8 byte를 가집니다. 각 시스템의 주소 값의 크기가 각각 4 byte, 8byte이기 때문입니다.

 

아래와 같이 확인 해 볼 수 있습니다.

#include <stdio.h>

int main(void)
{
	int* address;
	
	printf("%d", sizeof(address));
	
	return 0;
}

제 PC는 64 bit 시스템이므로 8이 출력되었습니다.

다음 글에서는 포인터와 배열의 관계에 대해 알아보겠습니다.

 

해당 내용은 윤성우 열혈 C프로그래밍을 참조하였습니다.

 

댓글