c언어에서 고차함수 프로그래밍기법
글쓴이: ozon1000 / 작성시간: 화, 2016/06/14 - 11:18오전
함수형 언어의 가장 큰 장점은 여러가지가 있겠지만, 가장 도드라진 특징은 모든 계산과 식들이 함수로 이루어져 있다는 것이지요.
그래서 프로그램에서 함수를 한번 정의하면 재사용이 쉽고 또한 함수합성(고차함수)프로그래밍이 가능하여, 비교적 프로그램이 간결하고 짧아지는 장점이 있습니다.
c언에서 비슷한 기법을 사용할 수 없을까? 라는 의문을 품고 관련 논문을 찾아보던 도중에 함수 포인터를 이용하면 가능하다는 글을 읽고 Test중인데...
이해안가는 코드가 있네요.. 한번도 c언어를 사용하면서 이렇게 사용한적은 없어서요!
형님들! 코드 설명좀 부탁드립니다.
참고로 main문 안의 g = test1(); 에서는 문법오류가 생기지만 정상적으로 build는 되며 실행됩니다.
실행되면 콘솔창에 22라고 뜨게되지요.
제생각에는 원리도 간단한데... main문안의 내용이 이해가 잘 가지 않습니다.
부탁드립니다.!
#include <stdio.h> void *test1(); void test2(); int main() { void(*g)(void); g = test1(); g(); } void *test1() { return test2; } void test2() { printf("22\n"); } <\code>
Forums:
타입을 정확히 맞추면
이렇게 고치면 컴파일러 에러 옵션을 아무리 민감하게 설정해도 아무 문제 없을겁니다.
원래 코드에서는 함수 포인터를 암시적으로
void *
로 캐스팅하고, 그걸 또 다시 함수 포인터로 암시적 캐스팅하고 있었거든요.아무리 함수 포인터가 직접 다루기에는 타이핑하기 귀찮은 물건이라고는 하지만, 어쨌거나 합법적으로 할 방법이 있긴 있는겁니다.
C언어의 함수 규칙은 재귀적이고 유연하기 때문에, 마음만 먹으면 이상(李箱) 작가님의 시 마냥 "함수포인터를반환하는함수의포인터를반환하는함수의포인터를반환하는함수" 같은 걸 문제 없이 사용할 수 있습니다.
물론 정 C언어로 시를 쓰고 싶은 생각이 아니라면, 혹은 ioccc(http://www.ioccc.org/) 같은 데 출품할 작품을 만들고 계시는 게 아니라면 그런 코드는 최대한 지양하는 편이 바람직하지요. 설령 어지간한 이유가 있다고 한들, 가독성 및 유지보수 측면에서 그런 코드는 점수가 마이너스거든요.
조금 바로잡습니다.
C언어의 함수 규칙은 재귀적이고 유연하기 때문에
-> C언어의 "타입" 규칙은 재귀적이고 유연하기 때문에.
바로잡는 김에 조금 더 덧붙이자면, C언어에서 함수 타입과 함수 포인터 타입 사이에는 미묘한 문법적 허용이 있습니다.
올려드린 코드를 주의깊게 보신다면 아마 눈치채셨을 텐데, 솔직히 저도 코드 고치는 동안 깜빡해버려서 뒤늦게 덧붙이네요.
1)
test1
의 반환값과 반환 타입을 보세요.test2
를 반환하고 있는데, 이건 엄밀히 말하면 함수 포인터가 아니라 함수 타입이죠.엄밀히 따지면
&test2
와 같이 주소를 취해서 반환해야 합니다.근데 그냥
test2
라고만 써도 알아서 포인터로 변환됩니다.2)
main
에서 함수 포인터g
를 호출하는 부분도 잘 보시길.g
는 엄밀히 말해 함수가 아니라 함수 포인터이고, 따라서 엄밀히 따지면 함수 호출 연산자()
가 바로 붙을 수 없었어야 하죠.게다가 후위 연산자가 전위 연산자보다 먼저 결합한다는 규칙에 의해, 엄밀히 따지면
(*g)();
처럼 썼어야 했어요.하지만
g();
와 같이 함수 포인터에다 대고 함수 호출 연산자를 적용해도 알아서 호출됩니다.이 두 미묘한 점은 마치 배열과 포인터 사이의 미묘한 관계를 연상케 합니다. 대개의 실용적인 함수 포인터 프로그래밍에서 신경쓰이는 부분은 아니긴 합니다만, 저처럼 pedantic한 프로그래밍 언어 문법 덕후들에게는 가끔 가다 떠올라서 신경쓰이게 만드는 부분이죠.
음 ..
function pointer 를 리턴하는 함수는 다음과 같이 쓰면 됩니다.
그런데 이렇게 쓰는 거는 가독성이 별로라서, 가급적 typedef 를 쓰는게 낫습니다.
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
헉...;
여기까지는 생각을 못 했군요.
저는
void (*)(void) test(void)
정도로 기대하고 있었었는데 말이죠.으 역시 배우고 또 배워도 끝이 없는 미묘한 C syntax...
일관성이 있다면 있다고 볼 수도 있겠습니다만 가독성은 정말 별로로군요. 레알 건축무한육면각체 수준...
ymir님의 답변에 질문이 있습니다.
테스트 중인데 확실히 typedef를 쓰는데 가독성 및 코드 수정 등에 유리하더라고요.
생전 처음 보는 문법을 테스트 하는 중이라 그런지 어려움이 좀 있습니다.
코드를 한번 봐 주세요.
이게 너무 헷갈리는게 test1의 함수는 test2의 함수 포인터를 리턴해야 하는게 맞지 않나요?
main문 안의 g의 선언을 보시게 되면 나중에 함수 포인터를 받은 g는 두개의 int를 가져갈 수 있는데요.
이럴때 g의 선언에 뒤에 void 가 아닌 int, int라고 써도 괜찮은 건가요?
ymir의 코드와 제가 밑에 올려놓은 코드를 보고 코멘트를 달아주실 수 있으시나요?
읽어주셔서 감사합니다.
제가 ymir님은 아닙니다만
C언어에서의 선언은 선언 대상을 감싸는 형태라고 보시는 편이 좋습니다.
즉, 선언 대상에 가까운 것부터 적용됩니다.
A (*i(B))(C)
와 같은 선언이 있을 때,(B)
가 먼저i
에 결합하여 i를 "함수"로 만듭니다. (후위에 있는 것이 먼저 결합)매개변수로
(B)
를 받는 함수요. 그럼 무엇을 리턴하느냐?*
, 즉 포인터.무엇에 대한 포인터냐면,
(C)
를 받는 함수.그 함수의 반환값은
A
타입이 되는 셈이죠.즉 정리하자면
i
는(B)
를 매개변수로 받으며,(C)
를 매개변수로 받아A
를 반환하는 함수에 대한 포인터를 반환하는 "함수"가 되는 겁니다.C언어의 함수 선언 규칙은 참 복잡하고 재밌죠. 이런 퍼즐은 퍼즐로서 즐기고, 실용 코딩 때는 지양하는 편이 좋습니다.
한참 머리굴려서 코딩해 놓고 몇 주 뒤에 다시 보면 까먹어서 다시 풀어야 하거든요. -_-;
감사합니다 "익명"님
한참 이것저것 만져보는 대학원생으로서,
심오한(?) 깊은 생각을 하게 해준 유익한 시간이었습니다.
친절하고 세밀한 답변 다시 한번 감사드립니다.
아래와 같이 바꾸면 좀 생각하기
아래와 같이 바꾸면 좀 생각하기 편하실까요?
test1()은 인덱스를 받아서 그에 해당하는 함수포인터를 골라 반환해주는 브로커(?) 역할을 합니다.
main 함수에서는 함수포인터인 g를 정의해 두고 브로커인 test1()를 통해 넘겨받은 함수포인터를 g에 할당한 다음, g에 저장한 함수를 실행합니다.
위에 익명님 댓글에서 배울 점이 많네요.. 그 댓글을
위에 익명님 댓글에서 배울 점이 많네요..
그 댓글을 탐독하시면 되겠습니다
이맛에..
KLDP를 끊지 못하는것 같아요.. ㅎㅎ
평소와 다르게 좀더 깊이 생각하게 하는 계기를 만들어주니 참 좋습니다~ !
대단하십니다. 감명(?)받았습니다.
세상은 넓고 능력자는 많고!
멋지십니다. 답변 감사드립니다. 많은 도움이 되었습니다.
c / c++을 수박 겉핥기식으로 익히며 사용했다는 느낌이 팍팍(?) 들게하는 답변이었습니다.
댓글 달기