지금까지 변수에 관한 포인터를 이야기했었다면 이번엔 다른 포인터를 알아보자.
함수 포인터
프로그램의 모든 변수는 역할에 맞게 메모리에 할당되는데, 변수뿐만이 아니라 함수 또한 메모리에 할당된다.
이를 통해 함수도 하나의 포인터라고 볼 수 있는 것이다. 물론 배열처럼 상수형 포인터이다. 하지만 지금까지 선언했던 포인터변수와는 조금 다른 방식으로 선언하게 되는데, 이러한 포인터 변수들을 함수 포인터 변수라고 한다.
함수 포인터 변수를 선언하기 위해서는 우선 함수 포인터의 포인터형을 결정해야 한다. 모든 변수가 그렇듯이 형을 결정하지 못하면 포인터변수를 선언할 수 없으니까 말이다.
다음 함수를 통해 형을 결정해보자.
int func (int num) {...}
이 함수를 뜯어보면 다음과 같은 정보를 알 수 있따.
- 반환형 : int
- 이름 : func
- 매개변수 : int num
이것만 보고 함수 포인터의 형을 결정하기는 참으로 난감하다. 그렇기 때문에 2차원 배열에서도 그랬듯이 결국 함수 포인터도 그 형을 한번에 정의할 수 있는 무언가가 있는 것은 아니다.
그래서 함수 포인터는 반환형과 매개변수에 초점을 맞추어 그 형을 결정한다. 그렇기에 func함수의 포인터형은 다음과 같을 것이다.
반환형은 int이고 매개변수로 int형 변수가 하나 선언된 포인터 형이다.
그렇다면 선언은 어떻게 해야할까? 앞서 말했듯이 함수 포인터의 형은 반환형과 매개변수를 기준으로 맞춰진다. 이 정보들을 모두 가져야 하기 때문에 이를 모두 부여하는 함수 포인터 변수는 다음과 같이 선언된다(앞서 선언한 func함수를 예로 들겠다).
int (*ptr) (int);
여기서 맨처음에 나오는 int는 반환형, ptr은 포인터, (int)는 매개변수의 선언이다. 또한 이 포인터 변수에 func함수의 주소값을 대입하면 func함수와 동일하게 작동한다.
int (*ptr) (int);
ptr = func;
ptr(1); // func(1)과 동일
예제를 만들어서 좀더 확인해보자.
#include <stdio.h>
void string_print (char * str) {
printf("%s \n", str);
}
int add (int a, int b) {
return a+b;
}
int main () {
int num1 = 10, num2 = 20, sum;
char * a = "test";
void (*ptr1)(char * ) = string_print;
int (*ptr2)(int ,int) = add;
ptr1(a);
sum = ptr2(num1, num2);
printf("%d \n", sum);
return 0;
}
실행결과
test
30
또한 함수 포인터 변수도 변수이기 때문에 다른 함수의 매개변수로 사용하는것이 가능하다.
#include <stdio.h>
void what_is_max (int n1, int n2, int (*get_max) (int, int)) {
printf("%d\n", get_max(n1, n2));
}
int max(int a, int b) {
return a > b ? a : b;
}
int main () {
int num1 = 10, num2 = 20;
what_is_max(num1, num2, max);
return 0;
}
실행결과
20
형이 존재하지 않는 void 포인터
void형 포인터는 다음과 같이 선언된다.
void * ptr;
void형 포인터는 어떤값이던 들어갈 수 있는 포인터 변수이다. 변수의 주소도 가능하고, 함수의 주소도 가능하다.
정말 엄청난 만능도구인 것 처럼 보이지만, 단점이 존재하는데, void형 포인터는 어떠한 포인터 연산도 진행할 수 없다. void형 포인터 안에 저장된 주소값을 통한 값의 참조 및 변경조차 불가능하다. 생각해보면 당연하다. 주소만 담고 있을 뿐 어떠한 형에 대한 정보도 없기 때문에 무엇을 기준으로 메모리에서 읽어올 것인가.
그럼 어디다가 쓰라는거냐! 할 수도 있지만 후에 배울 메모리의 동적 할당과 정말 깊은 관계라고 하니 일단은 이런 중요한 친구가 있다고 기억하고 가자.
main함수로의 인자 전달
main함수를 정의하는데에는 다음과 같은 방식들을 사용한다.
int main() {....}
int main(void) {....}
하지만 main함수는 제한적인 인자전달을 허용하는데, 다음과 같다.
int main (int argc, char * argv[]) {....}
이를 확인하기 위해서는 보통의 방법으로 알아보기는 힘들다. 일단 다음 예제를 저장한 후에 컴파일하여 실행파일을 만들자.
#include <stdio.h>
int main (int argc, char * argv[]) {
printf("전달받은 문자열의 개수 : %d \n", argc);
for (int i = 0; i < argc; i++) {
printf("%d번째 문자열 : %s \n", i, argv[i]);
}
return 0;
}
필자는 arg라는 이름으로 컴파일하여 arg.exe파일을 만들었다.
이제 윈도우 기준으로 cmd를 열어 컴파일한 실행파일이 있는 곳으로 이동하자. 이동하는데 성공했다면 다음과 같이 적어보자.
(실행파일 이름) hello C C++
그러면 다음과 같이 실행될 것이다.
일단 실행과정에서 입력한 문자열이 프로그램으로 전달이 되었다는 것은 확실하다.
main함수로 인자가 전달되는 과정을 살펴보자.
일단 우리가 입력한 구문을 보자
(실행파일 이름) hello C C++
실행파일이 현재 위치한 경로에 있다면 실행파일의 이름은 실행파일을 실행시키는 명령어이다. 이 구문을 통해 실행되면 공백을 기준으로 4개의 문자열이 전달된다.
arg, hello, C, C++
이를 temparr라는 예시를 위한 char * 배열에 저장된다.
char * temparr[] = { "arg\0", "Hello\0", "C\0", "C++\0", NULL }
또한 전달받은 문자열의 개수는 첫번째 인자인 int argc에 저장된다.
이러한 배열이 구성된 후에 main함수는 다음과 같이 호출된다.
main (4, temparr);
또한 문자열을 전달할때 큰 따옴표로 묶으면 하나의 문자열로 전달된다. 다음과 같이 적어보자.
(실행파일 이름) "hello C C++"
공백으로 구분되던 hello C C++이 하나의 문자열로 묶여있는 것을 볼 수 있다.
'Language > C' 카테고리의 다른 글
포인터(完) - 연습문제 풀이 (0) | 2021.09.08 |
---|---|
포인터(8) - 下 (0) | 2021.09.05 |
포인터(8) - 上 (0) | 2021.09.04 |
포인터 (7) + 간단한 문제풀이 (0) | 2021.09.02 |
포인터(6.5) (0) | 2021.09.01 |