본문 바로가기

C언어/C언어 문법

[C언어] 포인터 (단일, 다중포인터, 주소값, 참조와 역참조, 포인터배열)

728x90
반응형

 포인터

 

모든 변수는 메모리 상에 할당되는 공간을 갖는다.

 

이 공간의 크기는 자료형에 따라 다르게 할당되고 각각의 자료형이 할당되는 위치는 모두 다르다.

 

이때 변수가 할당되어 있는 위치를 나타내는 것이 주소값이다.

 

다시 말해서 포인터는 변수가 메모리 상에 어느 위치에 저장되어 있는지를 나타낸다고 할 수 있다.

그리고 이것을 포인터가 변수를 '참조'하고 있다고 한다.

 

 

 

 

 

 

 

 

 

 

주소값과 포인터

 

주소값은 32비트 시스템의 경우에는 8자리의 16진수 0x00000000 ~ 0xFFFFFFFF

64비트 시스템의 경우에는 16자리의 16진수 0x0000000000000000 ~ 0xFFFFFFFFFFFFFFFF

으로 이루어져 있다.

 

이때 주소값이 나타내는 의미는 변수가 저장된 공간의 시작하는 부분이다.

 

따라서 long형 변수를 선언하고 그 주소값을 확인하였을 때 주소값이 0x000000000062FE14 라면

0x000000000062FE14 부터 0x000000000062FE17 까지 4바이트의 공간이 할당된 것이라고 볼 수 있다.

 

포인터 변수의 자료형은 변수가 저장된 주소로부터 어디까지가 할당된 공간인지 파악하기 위해 필요하다.

 

따라서 포인터 변수의 자료형은 변수의 자료형과 일치해야 한다.

 

int형 변수의 주소값을 갖는 포인터 변수는 int형으로 선언해야 하고

double형 변수의 주소값을 갖는 포인터는 double형으로 선언해야 한다.

 

 

 

 

 

 

 

 

 

&연산자

 

&연산자는 피연산자의 주소값을 반환하는 연산자이다.

 

주소값은 %p 서식문자를 사용하여 확인할 수 있다.

 

#include <stdio.h>

int main()
{
	int num = 0;
	printf("%p", &num);
	return 0;
}

 

따라서 위 코드는 num이 메모리상에 저장된 위치를 출력하는 코드이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

포인터 변수 선언

 

포인터 변수도 다른 변수와 마찬가지로 선언할 때 자료형과 변수 이름이 필요하다.

 

그리고 자료형 다음에 *를 사용하여 포인터 변수임을 명시해주어야 한다.

 

*의 위치는 자료형 다음에 쓰기만 하면 *의 앞뒤로 한 칸씩 띄거나 붙여도 상관이 없다.

 

따라서 int형 변수 num의 주소값을 나타내는 포인터 변수는 다음과 같이 선언할 수 있다.

 

#include <stdio.h>

int main()
{
    int num;
    int* p1num = &num;
    int * p2num = &num;
    int *p3num = &num;
    return 0;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

참조와 역참조의 구분, 그리고 역참조 연산자

 

처음에 말했듯이 포인터가 변수를 가리키고 있는 것

이것이 '참조'라고 하였다.

 

그렇다면 역참조는 무엇일까?

 

포인터가 변수를 가리키고 있는 것

즉, '참조'를 이용하여 포인터를 통해 변수에 간접적으로 접근하는 것

이것이 '역참조'이다.

 

역참조를 할 때 사용하는 연산자를 역참조 연산자라고 한다.

 

 

정리하자면 역참조 연산자 *는 포인터가 가리키는 메모리 공간에 접근할 때 사용하는 연산자이다.

 

 

 

 

#include <stdio.h>

int main()
{
    int num = 5;
    int* pnum = &num;
    printf("%p %d\n", pnum, pnum);
    printf("%d\n", *pnum);
    *pnum = 10;
    printf("%d\n", *pnum);
    printf("%d", num);
    return 0;
}

 

 

위 코드의 실행 결과는 아래와 같다.

 

 

 

위 코드에서 pnum 에는 num의 주소값이 저장되어 있다.

 

첫 번째 출력인 62FE14(10진수로 6487572)를 통해서 우리는 num의 주소값을 확인할 수 있다.

 

 

*연산자를 사용하면 포인터변수가 가리키는 변수에 간접적 접근이 가능하다고 했다.

 

두 번째로 *pnum을 출력해 봄으로써 num에 접근할 수 있다는 것을 확인했다.

 

 

이후 *pnum을 수정하여 값에 간접적으로 접근하여 수정하는 것이 가능한지 확인해 보겠다.

 

세 번째와 네 번째 출력을 통해 수정한 값이 num에 그대로 적용된 것을 확인할 수 있다.

 

 

 

 

 

 

 

 

 

 

지금까지 설명한 내용을 간단히 정리하자면 아래 그림과 같다.

 

 

 

 

 

 

 

 

 

 

 

 

다중포인터

 

포인터변수도 변수이기 때문에 주소값이 존재한다.

 

그렇다면 포인터변수의 포인터도 만들 수 있을까?

 

 

당연히 가능하다.

이것을 이용한 것이 바로 이중포인터이다.

 

이중포인터를 선언하는 방법은 포인터변수를 선언할 때보다 *을 한 개 더 붙여주면 된다.

 

int **dptr = &ptr;

 

 

 

 

 

 

이제 이중포인터에 실제 값을 넣어 확인해 보겠다.

 

#include <stdio.h>

int main()
{
	double num = 10.5;
	double *ptr = &num;
	double **dptr = &ptr;
	
	
	printf("%lf\n", **dptr);
	printf("%lf\n", *ptr);
	printf("%lf\n\n", num);
	
	printf("%p\n", *dptr);
	printf("%p\n", ptr);
	printf("%p\n\n", &num);
	
	
	printf("%p\n", dptr);
	printf("%p", &ptr);
	
	return 0;
}

 

 

 

실행 결과는 다음과 같다.

원리는 단일포인터와 동일하므로 굳이 설명하지 않겠다.

 

이것을 응용하면 이중포인터뿐만 아니라 삼중, 사중, n중포인터를 만드는 것도 가능하다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

포인터배열

 

포인터도 다른 자료형들과 마찬가지로 배열로 선언할 수 있다.

 

선언하는 방법도 다른 자료형들과 같이 변수명 뒤에 대괄호를 사용해주면 된다.

 

int *arr[5] = {&num1, &num2, &num3, &num4, &num5};

 

 

 

 

 

 

 

 

 

 

 

 

 

포인터 변수의 크기

 

위에서 말했듯이 포인터 변수는 32비트 시스템과 64비트 시스템에서 크기가 다르다.

 

따라서 포인터 변수는 가리키는 주소에 위치하는 변수의 자료형에 상관없이

32비트 시스템에서는 4바이트, 64비트 시스템에서는 8바이트의 크기를 갖는다.

 

이 내용을 아래 코드를 실행시킴으로써 확인해 볼 수 있다.

 

#include <stdio.h>

int main()
{
	int num = 10;
	int* p = &num;
	printf("%d", sizeof(p));
	return 0;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

포인터의 잘못된 사용

 

포인터를 선언할 때 아래 코드처럼 작성하면 오류를 발생시킬 수 있다.

 

int * p;
*p = &num;

 

위 코드가 의미하는 것을 설명해보겠다.

 

먼저 p라는 포인터변수를 선언했는데 초기화를 하지 않았으므로 쓰레기값이 들어가 있을 가능성이 있다.

만약 그렇다면 p라는 포인터변수가 우연히 임의의 변수를 가리키고 있을 수도 있다.

 

그런데 여기서 두 번째 줄의 코드는 p에 num의 주소를 기록하는 것이 아니라

*p에 num의 주소를 기록하겠다는 의미가 된다.

 

즉 임의의 사용 중인 변수의 값을 지워버리고 그 변수에 난데없이 num의 주소값을 대입해 버릴 수도 있는 상황인 것이다.

 

 

따라서 포인터변수의 선언과 대입을 분리하고 싶다면 아래 코드처럼 작성해야 옳다.

 

int * p;
p = &num;

 

 

 

 

 

 

 

 

 

 

 

포인터를 사용하는 이유

 

지금까지 포인터에 대한 전반적인 소개를 해봤는데 포인터를 공부하다보면 이런 의문이 들 수 있다.

 

변수에 직접적으로 접근해서 사용하면 되는데 굳이 왜 간접적으로 접근하기 위한 포인터가 필요한 것인가?

 

 

함수를 공부해보면 알 수 있는 내용인데 함수에 값을 전달할 때 값을 직접적으로 전달하는 것이 아니라

복사하여 함수에 전달해주는 것이다.

 

그렇기 때문에 함수를 사용하여 변수를 직접적으로 수정하는 것은 불가능하다.

 

이 때 간접적 접근 방식인 '역참조'를 사용하는 것이다.

 

 

혹은 자료구조를 공부하는 데에 사용될 수도 있다.

 

 

 
 

 

 

 

 

 

 

 

 

728x90
반응형

'C언어 > C언어 문법' 카테고리의 다른 글

[C언어] 포인터 연산  (0) 2024.02.12
[C언어] 배열의 주소  (0) 2024.02.12
[C언어] 2차원배열, 다차원배열  (0) 2024.02.11
[C언어] 배열  (0) 2024.02.11
[C언어] 지역변수, 전역변수  (0) 2024.02.11