모노산달로스의 행보
[C programming] 포인터와 다차원 포인터 선언과 사용, 함수포인터, 주소 가감산법 본문
C programming - 포인터 선언과 사용
리눅스 환경에서 네트워크 프로그래밍을 공부하기 위해서 C언어를 다시 복습해야 할 필요성을 느꼈습니다. 따라서 이번 기회에 배열부터 전처리기까지 내용들을 정리하겠습니다.
포인터란 무엇인가?
이름은 많이도 접한 포인터 과연 어떤 역할을 하는 것일까요? 포인터를 사용해 메모리 주소를 참조하면 다양한 자료형 변수들에 접근하기 쉬워집니다. 또한 배열과 같은 연속된 데이터 혹은 동적으로 할당된 메모리 영역(힙 영역)을 조작하기 용이합니다.
글로만 보아서는 이해하기 힘든 개념이라고 생각합니다. 지금은 단순히 주소를 저장하는 변수라고 할 수 있습니다.
포인터 변수의 선언과 사용
포인터의 선언 방법을 살펴보면서 조금 더 자세하게 알아보겠습니다.
int* pointer = NULL;
여타 변수들을 선언하는 방식과 동일하게 자료형과 포인터 변수의 이름 그리고 값을 초기화하면 됩니다. 한 가지 특징으로는 자료형에 *을 붙여주어 포인터 변수임을 나타낼 수 있습니다. 또한 포인터 변수를 초기화하는 경우 NULL을 사용합니다.
아래는 실제로 포인터를 선언하는 예제입니다.
#include <stdio.h>
int main(void)
{
char* cp = NULL;
int* ip = NULL;
printf("%x %x %x\n", &cp, cp, *&cp);
printf("%x %x %x\n", &ip, ip, *&ip);
printf("%d %d\n", sizeof(char*), sizeof(int*));
printf("%d %d\n", sizeof(cp), sizeof(ip));
return 0;
}
&cp의 경우 포인터 변수의 주소를 나타냅니다. 여타 변수들의 주소가 메모리에 할당되는 것 처럼 포인터 또한 변수이므로 자신의 주소를 가지고 있습니다.
cp와 *&cp는 무엇일까요? 바로 포인터가 가리키는 주소를 의미합니다. 여기서 포인터의 작동 방식이 나타납니다. 포인터는 항상 어떠한 위치를 가리키고 있습니다. 그 위치는 변수, 배열, 함수 어떤 것이든 가능합니다. 따라서 포인터를 호출하면 자신이 가리키는 위치의 주소를 반환하게 됩니다.
마지막으로 sizeof() 함수를 통해서 포인터 변수의 크기를 확인할 수 있습니다. 출력된 포인터 변수의 크기는 8바이트로서, int를 가리키는 포인터와 chat을 가리키는 포인터 상관없이 모두 동일하게 8바이트의 크기를 가지는 것을 알 수 있습니다. (64bit 환경에서는 8, 32bit 환경에서는 4가 출력됩니다.)
이제 이렇게 선언 한 포인터 변수를 실제로 사용하는 코드를 살펴보겠습니다.
#include <stdio.h>
int main(void)
{
char c='A';
char* cp = NULL;
cp = &c;
printf("%x %c %c \n", &c, c, *&c);
printf("%x %x %x \n", &cp, cp, *&cp);
printf("%c \n", c);
printf("%c \n", *cp);
return 0;
}
문자 'A'를 저장하는 변수 c와 문자 타입을 가리키는 포인터 cp를 선언했습니다. 그리고 cp = &c를 통해서 포인터 cp가 c의 주소를 가리키게 했습니다. 변수 c의 주소를 출력한 값과 cp의 출력 값이 같다는 것을 통해 cp가 변수 c의 주소를 가리킨다는 것을 확인할 수 있습니다.
위와 같은 과정을 거치면 우리는 두 가지 방법으로 값에 접근할 수 있습니다. 변수 c를 통해서 값을 출력하는 직접 접근과 특정한 값을 포인터를 통해서 접근하는 간접 접근이 가능합니다.
#include <stdio.h>
int main(void)
{
int* ip = NULL;
*ip = 100; // error
return 0;
}
위 코드는 포인터의 잘못된 활용의 예시입니다. 앞서 말했듯이 포인터는 주소를 저장하는 변수입니다. 저장된 주소가 없는 상황에서 값을 변경하면 에러가 발생하게 됩니다.
#include <stdio.h>
int main(void)
{
int* ip = 2427534; // error
*ip = 100;
return 0;
}
마찬가지로 포인터 변수에 이상한 주소를 저장하면 오류가 발생합니다.
다차원 포인터 변수
아래와 같이 다차원의 포인터 변수를 선언할 수 있습니다.
int* p1 = NULL; // 1차원 포인터 변수
int** p2 = NULL; // 2차원 포인터 변수
int*** p3 = NULL; // 3차원 포인터 변수
2차원 포인터 변수의 역할은 1차원 포인터 변수의 주소를 저장하는 것입니다. 마찬가지로 3차원 포인터 변수는 2차원 포인터 변수의 주소를 저장합니다. 즉, 아래와 같은 활용이 가능합니다.
#include <stdio.h>
int main(void)
{
int num1 = 10;
int* ip = NULL;
int** ipp = NULL;
ip = &num1;
ipp = &ip;
printf("%8d %8x %8x \n", num1, ip ,ipp);
printf("%8x %8x %8x \n", &num1, &ip ,&ipp);
printf("%8d %8x %8x \n", *&num1, *&ip ,*&ipp);
printf("%8d %8d %8d \n", num1, *ip ,**ipp);
printf("%8x %8x %8x \n", &num1, ip ,*ipp);
return 0;
}
10이라는 값을 가지는 num1변수와 이를 가리키는 ip 포인터가 존재합니다. 그리고 다시 한번 ipp 포인터 변수로 ip를 가리킵니다.
*ipp 를 출력하면 ipp가 가리키는 ip가 가지는 값이 출력됩니다. 즉, num1의 주소가 출력됩니다.
**ipp 를 출력하면 비로소 num1의 값에 접근할 수 있습니다. 즉, 차원의 수만큼 *를 붙여주어 가리키는 값에 접근이 가능합니다.
주소의 가감산
#include <stdio.h>
int main(void)
{
int num1 = 10;
int* ip = NULL;
int** ipp = NULL;
ip = &num1;
ipp = &ip;
printf("%x %x %x\n", &num1, &ip, &ipp);
printf("%x %x %x\n", &num1+1, &ip+1, &ipp+1);
printf("%d %x %x\n", num1, ip, ipp);
printf("%d %x %x\n", num1+1, ip+1, ipp+1);
return 0;
}
배열의 주소 참조에서 보았던 것 처럼, 주소에 값을 더하면 주소의 가감산이 일어나게 됩니다. 위에서 포인터는 8바이트의 크기를 가진다는 것을 배웠습니다. 따라서 ip의 주소인 6ee42fe0에 1을 더하면 6ee42fe8이 됩니다. 그리고 ip의 주소에 1을 더한 값은 ip가 가리키는 num1의 주소값과 같습니다.
함수 포인터
우리가 사용하는 함수들 또한 자신의 주소를 가지고 있습니다. 따라서 주소를 저장하는 변수인 포인터로 참조가 가능합니다. 각 함수의 이름은 함수의 시작 주소를 나타냅니다.
#include <stdio.h>
int main(void)
{
printf("%x %x %x \n", main, printf, scanf);
return 0;
}
함수 포인터는 이러한 함수의 시작 주소를 저장하는 변수입니다.
int (*pointer) (int, int)
함수 포인터는 위와 같은 형태로 선언합니다. 가리키는 대상 함수의 자료형을 가장 먼저 설정합니다. 이후 함수 포인터의 이름을 괄호와 *을 사용하여 표현합니다. 마지막으로 함수의 파라미터에 들어 갈 자료형을 설정합니다.
#include <stdio.h>
int main(void)
{
int x, z;
char c;
void (*pointer) (int, int);
scanf(“%d %c %d”, &x, &c, &z);
if(c=='+')
pointer=add;
else if(c=='-')
pointer=subtract;
pointer(x,z);
return 0;
}
함수 포인터를 사용하면 이러한 활용이 가능합니다. 조건문을 통해 입력에 '+'와 '-'가 들어있는지 판별합니다. 이후 함수 포인터 변수인 pointer에 알맞은 함수를 참조시킵니다. 예를 들어 '+'가 입력되면 pointer에 add함수를 참조시킵니다. 그리고 계산을 수행하게 됩니다.
이러한 함수 포인터는 일반적인 함수 호출보다 빠른 처리 속도를 기대할 수 있습니다. 주로 컴파일러, 인터프리터 ,게임 프로그래밍과 같은 속도가 중요시 되는 시스템 프로그래밍 분야에 사용됩니다.
포인터의 기본적인 내용을 모두 정리했습니다. 다음으로는 포인터 배열에 대해 알아보겠습니다.
'ProgrammingLanguage > C' 카테고리의 다른 글
[C programming] 표준 함수 (0) | 2024.04.16 |
---|---|
[C programming] 문자열을 가리키는 포인터, 포인터 변수 상수화 (0) | 2024.04.10 |
[C programming] 포인터를 통한 배열 값 접근, 배열 포인터와 포인터 배열 차이점, 값에 의한 호출과 참조에 의한 호출 차이점 (0) | 2024.04.10 |
[C programming] 다차원 배열(2차원 배열)의 정의, 선언, 초기화, 주소와 값의 참조 (0) | 2024.04.04 |
[C programming] 1차원 배열의 정의, 선언, 초기화, 주소와 값의 참조 (0) | 2024.04.02 |