[C언어] 포인터 - 11.19

산이·2023년 11월 19일

C언어

목록 보기
3/4


포인터

포인터(Pointer)란 어떠한 값을 저장하는 것이 아닌, 어떠한 값의 주소를 저장하는 것이다. 메모리의 주소값을 저장하는 변수이며, 포인터 변수라고도 부른다.

char형 변수가 문자를 저장하고,
int형 변수가 정수를 저장하는 것처럼 포인터는 주소값을 저장한다.

char word = 'alphabet';
int num = 100;

char *ptr = &word; //포인터 선언
int *ptr = # 

주소값의 이해

데이터의 주소값이란 해당 데이터가 저장된 메모리의 시작 주소를 의미한다.
C언어에서는 이러한 주소값을 1바이트 크기의 메모리 공간으로 나누어 표현한다.

예를 들어, int형 데이터는 4바이트의 크기를 가지지만
int형 데이터의 주소값은 시작 주소 1바이트만을 가리킨다.


포인터 연산자

C언어에서 포인터와 연관되어 사용되는 연산자는 다음과 같다.

주소 연산자 (&)

주소 연산자는 변수의 이름 앞에 사용하여, 해당 변수의 주소값을 반환한다.
'&'기호는 앰퍼샌드(ampersand)라고 읽으며, 번지 연산자라고도 불린다.

#include <stdio.h>

int main()
{
  int i = 5;
  char c = 73;
  float f = 11.9;
  
  printf("i의 주소:%u\n", &i);
  printf("c의 주소:%u\n", &c);
  printf("f의 주소:%u\n", &f);
  
  return 0;
}

/*[실행결과]
i의 주소: 012FFAOC
c의 주소: 012FFA03
f의 주소: 012FF9F0 
*/

간접 참조 연산자(*)

참조 연산자는 포인터의 이름이나 주소 앞에 사용해,
포인터에 가리키는 주소에 저장된 값을 반환한다.
C언어에서 '*'기호는 사용하는 위치에 따라 다양한 용도로 사용된다.

이항 연산자로 사용하면 곱셈 연산으로 사용되며,
포인터의 선언 시에 사용되면 포인터가 가리키는 값을 가져오는 연산자 된다.

#include <stdio.h>

int main()
{
  int i = 5;
  int *p;
  p = &i;
  printf("값은 %d", *p);
  
  return 0;
}

/*[실행결과]
값은 5
*/

포인터 사용시 주의사항

포인터의 선언

타입* 포인터이름;
  

타입이란 포인터가 가리키고자 하는 변수의 타입을 명시한다.
포인터 이름은 포인터가 선언된 후에 포인터에 접근하기 위해 사용된다.

포인터를 선언한 후 참조 연산자(*)를 사용하기 전에
포인터는 반드시 먼저 초기화되어야 한다.
그렇지 않으면 의도하지 않은 메모리의 값을 변경하게 되기 때문이다.
C 컴파일러는 초기화하지 않은 포인터에 참조 연산자를 사용하면 오류를 초래한다.

따라서 다음과 같이 포인터의 선언과 동시에 초기화를 함께 하는 것이 좋다.

타입* 포인터이름 = &변수이름;
 
 또는
 
타입* 포인터이름 = 주소값;

함수 호출시 인수 전달 방법

함수를 호출할 때에는 함수에 필요한 데이터를 인수(argument)로 전달해 줄 수 있습니다.
이렇게 함수에 인수를 전달하는 방법에는 크게 다음과 같이 두 가지 방법이 있습니다.

값에 의한 호출 (call-by-value)

값에 의한 호출은 인수로 전달되는 변수가 가지고 있는 값을 함수 내의 매개변수에 복사하는 방식이다. 이렇게 복사된 값으로 초기화된 매개변수는 인수로 전달된 변수와는 완전히 별개의 변수가 된다. 따라서 함수 내에서의 매개변수 조작은 인수로 전달되는 변수에 아무런 영향을 미치지 않는다.

#include <stdio.h>
 
void modify(int value)
{
 value = 99;
}
 
int main()
{
  int number = 1;
 
 modify(number);
 printf("number = %d\n", number);
 
 return 0;
}

/*[실행결과]
number = 1
*/

참조에 의한 호출 (call-by-reference)

참조에 의한 호출은 인수로 변수의 값을 전달하는 것이 아닌, 해당 변수의 주소값을 전달한다. 즉, 함수의 매개변수에 인수로 전달된 변수의 원래 주소값을 저장하는 것이다. 이 방식을 사용하면 인수로 전달된 변수의 값을 함수 내에서 변경할 수 있게 된다.

#include <stdio.h>
  
void modify(int* ptr)
{
  *ptr = 99;
}
  

int main()
{
  int number = 1;
  modify(&number);
  printf("number = %d\n", number);
 
  return 0;
}

/* [출력결과]
number = 99 
*/

배열과 포인터

포인터와 배열은 매우 긴밀한 관계를 맺고 있으며,
어떤 부분에서는 서로를 대체할 수도 있습니다.

#include <stdio.h>

int main()
{
  int arr[3] = {10, 20, 30}; // 배열 선언  
  printf("배열 이름을 이용하여 배열 요소에 접근 : %d %d %d\n", arr[0], arr[1], arr[2]);
  printf("배열 이름으로 포인터 연산 통해 접근 : %d %d %d\n", *(arr+0), *(arr+1), *(arr+2));

  return 0;
}
/* [실행결과]
배열 이름을 이용하여 배열 요소에 접근 : 10 20 30
배열 이름으로 포인터 연산 통해 접근 : 10 20 30 
*/

다양한 포인터

포인터의 포인터

포인터의 포인터란 포인터 변수를 가리키는 포인터를 의미한다.
참조 연산자(*)를 두 번 사용하여 표현하며 이중 포인터라고도 부른다.

#include <stdio.h>

int main()
{
  int num = 10;              // 변수 선언
	int* ptr_num = &num;       // 포인터 선언
	int** pptr_num = &ptr_num; // 포인터의 포인터 선언  

	printf("변수 num의 값은 %d입니다.\n", num);
	printf("포인터 ptr_num가 가리키는 주소에 저장된 값은 %d입니다.\n", *ptr_num);
	printf("이중 포인터 pptr_num가 가리키는 주소에 저장된 포인터가 가리키는 주소에 저장된 값은 %d입니다.\n",**pptr_num);  

  return 0;
}
/* [실행결과]
변수 num가 저장하고 있는 값은 10입니다.
포인터 ptr_num가 가리키는 주소에 저장된 값은 10입니다.
이중 포인터 pptr_num가 가리키는 주소에 저장된 포인터가 가리키는 주소에 저장된 값은 10입니다.
*/

함수 포인터

프로그램에서 정의된 함수는 프로그램이 실행될 때 모두 메인 메모리에 올라가게 된다.
이때 함수의 이름은 메모리에 올라간 함수의 시작 주소를 가리키는 포인터 상수가 된다. 이렇게 함수의 시작 주소를 가리키는 포인터 상수를 함수 포인터라고 부른다.

함수 포인터의 포인터 타입은 함수의 반환값과 매개변수에 의해 결정된다.
즉 함수의 원형을 알아야만 해당 함수에 맞는 함수 포인터를 만들 수 있습니다.

#include <stdio.h>
 
int main()
{
 
  double (*calc)(double, double) = NULL; // 함수 포인터 선언
  double result = 0;  
  double num01 = 3, num02 = 5;
  char oper = '*';  
 
  switch (oper)
  {
    case '+':
        calc = add;
        break;
 
    case '-':
        calc = sub;
        break;

    case '*':
        calc = mul;
        break;

    case '/':
        calc = div;
        break;

    default:
        puts("사칙연산(+, -, *, /)만을 지원합니다.");
  }  
   result = calculator(num01, num02, calc);
   printf("사칙 연산의 결과는 %lf입니다.\n", result);
   return 0;
}
/* [실행결과]
사칙 연산의 결과는 15.000000입니다.
*/

void 포인터

void 포인터는 일반적인 포인트 변수와는 달리 대상이 되는 데이터의 타입을 명시하지 않은 포인터이다. 따라서 변수, 함수, 포인터 등 어떠한 값도 가리킬 수 있지만, 포인터 연산이나 메모리 참조와 같은 작업은 할 수 없다. 즉, void 포인터는 주소값을 저장하는 것 이외에는 아무것도 할 수 없는 포인터이다.

또한, void 포인터를 사용할 때에는 반드시 먼저 사용하고자 하는 타입으로 명시적 타입 변환 작업을 거친 후에 사용해야 한다.

#include <stdio.h>
  
int main()
{
    int num = 10;
    void* p_num = &num;
    
    printf("변수 num가 저장하고 있는 값은 %d입니다.\n", (int*)p_num);
    printf("void 포인터 p_num가 가리키는 주소에 저장된 값은 %d입니다.\n", *(int*)p_num);
  
    *(int*)p_num = 20;  // void 포인터를 통한 메모리 접근  
    printf("void 포인터 p_num가 가리키는 주소에 저장된 값은 %d입니다.\n", *(int*)p_num)
  
 
    return 0;
}
/* [실행결과]
변수 num가 저장하고 있는 값은 10입니다.
void 포인터 ptr_num가 가리키는 주소에 저장된 값은 10입니다.
void 포인터 ptr_num가 가리키는 주소에 저장된 값은 20입니다. 
*/

NULL 포인터

0이나 NULL을 대입해 초기화한 포인터를 널 포인터(null pointer)라고 한다.
NULL 포인터는 아무것도 가리키지 않는 포인터라는 의미한다.


profile
_san2i

1개의 댓글

comment-user-thumbnail
2023년 11월 19일

포인터는 은산이 ㅋ 🫵

답글 달기