함수 별칭 관련 질문입니다.

zasxer의 이미지

"함수별칭"을
보통 같은 매개변수를 가진 함수를 대체하기 위해 쓰는 거로 알고 있는데 (아래와 같이)

#include "stdafx.h"
#include <stdio.h>
 
typedef int(*pf)(int, int);
 
int add(int a, int b) {
	return a + b;
}
 
int main(void) {
	pf pp;
	pp = add;
	printf("%d\n", pp(3, 4));
	return 0;
}

근데 sighander_t를 선언하고
sighander_t를 변수처럼 사용하는데 무슨 의미일까요???

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

.
sighandler_t signal(int signum, sighandler_t handler);
 의 이미지

C언어 문법을 따져 보면, "함수 별칭"이라는 게 특별히 따로 있지는 않습니다.

주어진 코드는 그저 typedef를 이용하여 함수 포인터 타입의 별칭을 만들고 사용했을 뿐입니다.

C언어 문법에 충분히 익숙해지기 전에는 위와 같은 용법을 이해하기 쉽지 않습니다.
하지만 일단 시도해 봅시다.

typedef int (*pf)(int, int);
pf pp;

위 코드에서 선언한 pp는 사실 아래와 같이 선언한 것과 같은 의미입니다.

int (*pp)(int, int);

이 때 pp는 "int 타입 매개변수를 두 개 받고 int를 받는 함수 타입"에 대한 포인터가 됩니다.
보시다시피, add가 바로 그런 함수 타입이지요.

int 타입 변수의 주소를 int 포인터에 담을 수 있듯, 위와 같은 함수 포인터는 add의 주소를 담을 수 있는 것입니다.

즉 아래와 같이 쓸 수 있는 것이지요:

int integer;
int add(int a, int b){
    return a + b;
}
 
int main(void){
    int *p;
    int (*pp)(int, int);
 
    p = &integer;
    pp = &add;
 
    return 0;
}

그런데 함수 포인터에는 좀 특이한 점이 있습니다.

// integer 및 add의 정의 위와 같음
 
int main(void){
    int *p;
    int (*pp)(int, int);
 
    // p = integer; // 이건 컴파일 안 됨.
    p = &integer;
    i * 10;
    // p * 10; // 이것도 컴파일 안 됨.
 
    pp = add; // 이건 컴파일 됨.
    pp = &add;
    add(3, 4)
    pp(3, 4); // 이것도 컴파일 됨.
}

보시다시피 i와 p는 뚜렷한 차이가 있는 반면, 왠지 add와 pp는 대개의 경우 서로 바꿔 쓸 수 있는 것처럼 보입니다. 이게 바로 종종 "함수 별칭"이라고 불리는 이유이지요.

사실 이게 가능한 건 C언어의 조금 자명하지 않은 규칙들 때문입니다:

(1) C언어에서 함수 타입을 가진 표현식(function designator 라고 합니다.)은 일부 예외 케이스를 제외하고는 곧바로 그 함수에 대한 포인터로 변환됩니다.

pp = add라고 쓰면, add 앞에 &를 붙이지 않아도 자동으로 add 함수의 주소가 pp에 담기게 된다는 말이지요.

예외가 딱 셋 있는데, sizeof, _Alignof, 그리고 unary &의 operand로 쓰이는 경우입니다. 근데 함수 타입에 sizeof이나 _Alignof를 적용하는 건 문법 위반이라, 실제로 사용되는 예외 케이스는 딱 하나뿐이라고 볼 수 있겠네요.

위 예외에 따라 &를 적용하기 전의 add는 함수 타입을 유지하므로 pp = &add이 컴파일 되는 것입니다.

(2) 함수 호출 구문 f(...)에서 f는 사실 함수 타입 표현식이 아니라 함수 포인터 타입 표현식을 받습니다. 그런데 왜 함수 타입 표현식 add를 이용해서 add(3, 4)를 호출할 수 있느냐고 묻는다면, (1)을 보세요.

아무튼, 여기까지 이해하셨다면 이제 더 이상 signal의 매개변수 형식이 의문스럽지 않을 겁니다.
typedef를 걷어 내고 나면 결국 아래와 같을 뿐이죠

sighandler_t signal(int signum, void (*handler)(int));

즉, int 타입 매개변수 하나(signum), 그리고 "int를 받고 반환값이 없는 함수 포인터" 하나(handler)를 받는 것입니다. signal syscall의 용도를 알고 계신다면, 당연히 해당 signal이 왔을 때 호출할 함수를 받는다는 것을 알 수 있겠지요.

... 답변이 길어졌습니다만, 원래 함수 포인터는 좀 복잡하고, typedef로 함수 포인터 별칭을 만드는 건 조금 더 복잡해서요. 설명이 부족했다면...

https://dojang.io/mod/page/view.php?id=601

zasxer의 이미지

아래 매개변수가 의미하는 바가 무엇인지 알게 되었습니다.
sighandler_t가 함수 별칭으로 쓰여서

sighandler_t signal(int signum, void (*handler)(int));

라고 하셨는데
앞의 반환하는 sighandler_t도 함수 별칭을 쓰고 있습니다.

앞의 sighandler_t 반환에 대해 어떻게 해석하면 좋을까요?

 의 이미지

아, 귀찮아서 슬쩍 설명 안 하고 넘기려고 했는데 지적하시는군요. :(

별 거 아닙니다. int 포인터를 반환하는 함수가 있을 수 있으면,

int a;
 
int *some_function(){
    return &a;
}

함수 포인터를 반환하는 함수도 있을 수 있죠.

int add(int a, int b){
    return a+b;
}
 
int (*weird_function(void))(int, int){
        return &add;
}

signal 함수도 마찬가지로 함수 포인터를 반환하는 함수입니다.
함수 원형을 typedef 없이 나타내면 아래와 같습니다:

void (*signal(int signum, void (*handler)(int)))(int);

signal syscall은 새 signal handler(의 함수 포인터)를 설정하면서 이전의 signal handler(의 함수 포인터)를 반환하므로, 이런 형식이 불가피한 것이죠.

근데 이런 식으로 써 두면 누가 한 눈에 알아보겠어요?
딱 보다시피 굉장히 비직관적인 형태의 문법이므로 웬만해서는 typedef를 쓰는 게 좋습니다:

typedef int (*fun_t)(int, int);
 
fun_t weird_function(void){
    return &add;
}
 
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

참고로 함수를 호출해서 반환받은 함수 포인터를 바로 연달아 호출하는 짓도 가능합니다:

int main(void){
    printf("%d\n", weird_function()(2, 3));
    return 0;
}

함수포인터를반환하는함수포인터를반환하는함수포인터를반환하는함수:

#include <stdio.h>
 
int add(int a, int b){
    return a+b;
}
 
int (*weird_function1(void))(int a, int b){
    return &add;
}
 
int (*(*weird_function2(void))(void))(int a, int b){
    return &weird_function1;
}
 
int (*(*(*weird_function3(void))(void))(void))(int a, int b){
    return &weird_function2;
}
 
int main(void){
    printf("%d\n", weird_function3()()()(2, 3));
    return 0;
}
zasxer의 이미지

진짜 감사합니다.
함수 자체를 반환한다고 생각치도 못했는데...
엄청 잘하시네요. 부러워요ㅠ
와 진짜 또 한번 존경합니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.