Function-like Macros

이전 메뉴에서 살펴본 매크로는 object-like 매크로라고 합니다. 실제 매크로가 유용하게 사용되는 것은 function-like 매크로입니다. function-like 매크로는 이름 뒤에 ( ) 괄호를 붙여서 만듭니다. 매크로를 #define 할때 한가지 주의할 점은 매크로 이름과 괄호 사이에 공백이 오면 안됩니다 ( 그러면 object-like 매크로가 됩니다 ). 하지만 매크로 사용 시에는 이름과 괄호 사이에 공백이 올 수 있습니다.

$ gcpp
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))     // OK

ARRAY_SIZE(myarr)       // OK
ARRAY_SIZE  (myarr)     // OK     이름과 괄호 사이에 공백이 올 수 있다
@

(sizeof(myarr) / sizeof((myarr)[0]))
(sizeof(myarr) / sizeof((myarr)[0]))
--------------------------------------------------

# 다음은 매크로 정의시 이름과 괄호 사이에 공백을 두어 object-like 매크로가 되어
# ARRAY_SIZE 값이 (x) (sizeof(x) / sizeof((x)[0])) 가 됩니다.

$ gcpp
#define ARRAY_SIZE (x) (sizeof(x) / sizeof((x)[0]))       // WRONG !

ARRAY_SIZE(myarr)
@                 

(x) (sizeof(x) / sizeof((x)[0]))(myarr)     # ARRAY_SIZE 값에 (myarr) 가 붙는다.

인수의 구분은 , 로 합니다.

기본적으로 매크로 정의에 사용된 매개변수 개수와 매크로 사용시 comma 로 구분되는 인수의 개수가 맞아야 합니다. 인수 값은 생략해도 오류가 되지 않습니다. whitespace 는 전처리기에서 token 으로 사용되지 않으므로 empty 와 같습니다.

$ gcpp                                   $ gcpp
#define FOOBAR(a, b) foo a b bar         #define FOOBAR(a, b) foo a b bar

FOOBAR (100, 200)                        FOOBAR (100, )   // OK
@                                        @

foo 100 200 bar                          foo 100 bar
--------------------------------------------------------------------

$ gcpp                                   $ gcpp
#define FOOBAR(a, b) foo a b bar         #define FOOBAR(a, b) foo a b bar 

FOOBAR ( , 200)     // OK                FOOBAR ( , )     // OK 인수값은 empty 가 될수있다.
@                                        @

foo 200 bar                              foo bar
--------------------------------------------------------------------

$ gcpp
#define FOOBAR(a, b) foo a b bar

FOOBAR()                // 1 개의 인수로 인식돼 오류
FOOBAR(100, 200, 300)   // 인수 개수가 매개변수 개수와 맞지 않으면 오류가 됩니다.
@
<stdin>:3:5: error: macro "FOOBAR" requires 2 arguments, but only 1 given

Quote 을 하면 하나의 인수가 됩니다.

$ gcpp
#define FOOBAR(a, b) foo a b bar

FOOBAR( '100, 200' )                // single quotes
FOOBAR( "100, 200" )                // quote 을 하여 하나의 인수가 된다.
@
<stdin>:3:20: error: macro "FOOBAR" requires 2 arguments, but only 1 given

매개변수 값으로 comma 를 포함하려면 ( ) 괄호를 사용하면 됩니다.

CHECK (a, b, c)                       // a 와 b 와 c 세 개의 인수 

CHECK ((a, b), c)                     // (a, b) 와 c 두 개의 인수

CHECK (array[x = y, x + 1])           // array[x = y 와 x + 1] 두 개의 인수

CHECK (array[(x = y, x + 1)])         // array[(x = y, x + 1)] 한 개의 인수

CHECK (func(100, 200, "foo") == 1)    // func(100, 200, "foo") == 1 한 개의 인수.

다음 JOIN 매크로는 인수로 전달된 type 과 name 을 하나로 합치는 역할을 합니다. 그런데 인수로 전달되는 type 값에 , 가 포함될 수 있기 때문에 별도의 UNPACK 매크로를 정의하여 처리하는 예입니다.

$ gcpp
#define UNPACK( ... ) __VA_ARGS__
#define JOIN( type, name ) UNPACK type name

JOIN( (std::map<int, int>), map_var );       // type 은 괄호를 사용해 하나의 인수로 전달
@

std::map<int, int> map_var;
-----------------------------------------

1. JOIN( (std::map<int, int>), map_var ) 는 
   ---->  UNPACK (std::map<int, int>) map_var 로 확장되고 
2. UNPACK (std::map<int, int>) 은 UNPACK( ... ) __VA_ARGS__ 매크로 정의에 의해
   ---->  std::map<int, int> 로 확장되어 
3. 최종 결과는 std::map<int, int> map_var; 가 됩니다.

또는 COMMA 매크로를 정의해 사용

$ gcpp
#define COMMA ,
#define FOO( type, name ) type name

FOO( std::map<int COMMA int>, map_var );
@

std::map<int , int> map_var;

매크로 확장은 연이어서 발생합니다.

다음의 경우를 보면 TEST(ch)((SMALLCASE(ch)) || (UPPERCASE(ch))) 로 확장되는데 이전에 SMALLCASE(X)UPPERCASE(X) 매크로 정의가 존재하므로 다시 확장이 이루어집니다.

$ gcpp
#define SMALLCASE(X) (((X)>='a') && ((X)<='z'))
#define UPPERCASE(X) (((X)>='A') && ((X)<='Z'))
#define TEST(X) ((SMALLCASE(X)) || (UPPERCASE(X)))

if (TEST(ch)) {
    printf("Entered character is an alphabet\n");
} else {
    printf("Entered character isn't an alphabet\n");
}
@

if ((((((ch)>='a') && ((ch)<='z'))) || ((((ch)>='A') && ((ch)<='Z'))))) {
    printf("Entered character is an alphabet\n");
} else {
    printf("Entered character isn't an alphabet\n");
}

call_with_1(twice)twice(1) 로 확장되는데 이전에 twice(x) 매크로 정의가 존재하므로 다시 (2 * (1)) 로 확장됩니다.

$ gcpp
#define twice(x) (2 * (x))
#define call_with_1(x) x(1)

call_with_1 (twice)     // ---->  twice(1) 로 확장  ---->  (2 * (1)) 로 확장된다.
@

(2 * (1))

#include <sys/select.h> 에 정의되어 있는 매크로로 FD_SET(foo, &bar) 는 터미널 토큰이 나올 때까지 연이어서 확장이 이루어집니다.

$ gcpp
#define FD_SET(fd,fdsetp) __FD_SET (fd, fdsetp)
#define __FD_SET(d,s) ((void) (__FDS_BITS (s)[__FD_ELT(d)] |= __FD_MASK(d)))
#define __FDS_BITS(set) ((set)->__fds_bits)
#define __FD_ELT(d) ((d) / __NFDBITS)
#define __FD_MASK(d) ((__fd_mask) (1UL << ((d) % __NFDBITS)))
#define __NFDBITS (8 * (int) sizeof (__fd_mask))
// typedef long int __fd_mask;
// typedef struct
// {
//     __fd_mask __fds_bits[1024 / (8 * (int) sizeof (__fd_mask))];
// } fd_set;

FD_SET(foo, &bar);     // fd foo 를 fd_set bar 에 더하는 역할을 합니다.
@

((void) (((&bar)->__fds_bits)[((foo) / (8 * (int) sizeof (__fd_mask)))] |= ((__fd_mask) 
(1UL << ((foo) % (8 * (int) sizeof (__fd_mask)))))));

Statement 의 특정 부분만 매크로로 정의해 사용할 수도 있습니다.

$ gcpp
#define PRINT2(file) fprintf (file, "%s %d",

PRINT2(stderr) p, 35)
@

fprintf (stderr, "%s %d", p, 35)

다음의 경우 첫 번째 CHECK_STRING_OVERRUN 아래에 있는 i = si + 1; 는 else 문에 포함되는 것이고 두 번째 CHECK_STRING_OVERRUN 아래에 있는 continue; 는 else 문에 포함되지 않는 것입니다.

$ gcpp
#define CHECK_STRING_OVERRUN(oind, ind, len, ch) \
if (ind >= len) { \
    oind = len; \
    ch = 0; \
    break; \
} else

CHECK_STRING_OVERRUN(i, si, slen, c)       // ---- (1)
i = si + 1;

CHECK_STRING_OVERRUN(i, si, slen, c);      // ---- (2)  ";" 포함
continue;
@

if (si >= slen) { i = slen; c = 0; break; } else
i = si + 1;     // else 문에 포함됨
if (si >= slen) { i = slen; c = 0; break; } else;
continue;       // else 문에 포함되지 않으므로 항상 실행됨

매개변수와 전체 식은 괄호처리를 해야 합니다.

C/C++ 에서 함수를 호출할 때 인수로 expression 이 사용되면 먼저 연산이 완료된 후 결과값이 전달되지만 매크로의 경우는 expression 자체가 그대로 전달되어 치환됩니다. 따라서 매크로 확장 결과 연산자 우선순위 문제가 발생할 수 있으므로 항상 매개변수와 전체 식은 괄호처리를 해야 합니다.

다음 ceil_div(x, y) 매크로 정의를 보게 되면 매개변수에 해당하는 x, y 에 괄호처리를 하지 않아 매크로 확장 결과가 연산자 우선순위를 고려했을 때 (1) 번과 같게 됩니다. 이것은 결과적으로 틀린 식이 됩니다. (2) 번과 같아야 올바른 식이 됩니다.

$ gcpp
#define ceil_div(x, y) (x + y - 1) / y

res = ceil_div (b & c, sizeof (int));
@

res = (b & c + sizeof (int) - 1) / sizeof (int);          # 매크로 확장 결과

res = (b & (c + sizeof (int) - 1)) / sizeof (int);        ----- (1)

res = ((b & c) + sizeof (int) - 1)) / sizeof (int);       ----- (2)

따라서 다음과 같이 x, y 매개변수에 괄호처리를 해주면 올바른 식이 출력됩니다.

$ gcpp
#define ceil_div(x, y) ((x) + (y) - 1) / (y)           // x, y 매개변수 괄호처리

a = ceil_div (b & c, sizeof (int));
@

res = ((b & c) + (sizeof (int)) - 1) / (sizeof (int));     # OK

위의 경우는 x, y 매개변수에 괄호처리를 해서 문제를 해결했는데 만약에 ceil_div(x, y) 연산 결과를 sizeof 하려고 한다면 어떻게 될까요? 다음과 같이 sizeof 에서 / (y) 부분이 제외되게 됩니다. 따라서 전체 식도 괄호처리를 해줘야 합니다.

sizeof ceil_div(x, y)

sizeof ((x) + (y) - 1) / (y)         # sizeof 에서 / (y) 가 제외된다.

----------------------------

$ gcpp
#define ceil_div(x, y) (((x) + (y) - 1) / (y))      // 전체 식도 괄호처리

sizeof ceil_div(x, y)
@        

sizeof (((x) + (y) - 1) / (y))

Duplicate side effects 가 발생할 수 있습니다.

만약에 min(X, Y) 가 C/C++ 함수라면 인수에 사용된 x++foo(y) 는 min 함수 실행전에 한번만 실행이 되지만 매크로의 경우는 expression 이 그대로 전달되어 치환되므로 아래 확장 결과에서 볼 수 있듯이 해당 expression 이 여러번 실행될 수 있습니다.

$ gcpp
#define min(X, Y)  ((X) < (Y) ? (X) : (Y))

next = min(x++, foo(y));
@

next = ((x++) < (foo(y)) ? (x++) : (foo(y)));   # x++ 와 foo(y) 가 두번씩 실행될 수 있다.

따라서 이때는 ({ ... }) compound statment expression 을 사용해 문제를 해결할 수 있습니다.

compound statment expression 은 마지막 statement 값이 사용됩니다.

// x++ 와 foo(y) 를 _x, _y 임시 변수에 대입해 사용하므로 한번만 실행이 된다.

#define min(X, Y) \
({ typeof (X) _x = (X); \
   typeof (Y) _y = (Y); \
   (_x < _y) ? _x : _y; })

---------------------------------

#define min(X, Y) \
({ __auto_type _x = (X); \        // __auto_type 은 뒤에 바로 identifier 가 옵니다.
   __auto_type _y = (Y); \        // 매크로 인수로 매크로가 사용되는것 같이 nesting
   (_x < _y) ? _x : _y; })        // 될경우 typeof 에 비해 확장결과가 줄어듭니다.

---------------------------------

$ gcc -xc - <<\@ && ./a.out
#include <stdio.h>
int main()
{
    int a = 5;
    int b = ({          // compound statement expression
        int c; 
        c = a * a; 
        c + 3;          // 마지막 statement 값이 사용된다.
    });
    printf("a: %d, b: %d\n", a, b); 
}
@
a: 5, b: 28

typeof 는 매크로가 아닙니다. sizeof 같이 컴파일러에 의해 처리됩니다.
( __typeof__ , __typeof , typeof 모두 같은 것입니다. )

// 다음 s1, s2, s3, s4, s5 는 모두 같은 타입이 됩니다.
typeof(typeof(typeof(char) *) [10]) s1;
typeof(typeof(char *) [10]) s2;
typeof(char* [10]) s3;
char* s4[10];

#define pointer(T)  typeof(T *)
#define array(T, N) typeof(T [N])
array(pointer(char), 10) s5;       --->  typeof(typeof(char *)[10]) s5; 로 확장

---------------------------------------------------------------

int* foo, bar;                 # foo 는 타입이 int*, bar 는 int
typeof(int*) foo, bar;         # foo 는 타입이 int*, bar 는 int*
typedef int* pInt;
pInt foo, bar;                 # foo 는 타입이 int*, bar 는 int*

리눅스 커널 소스에서 볼 수 있는 container_of 매크로는 특정 구조체 멤버 주소를 이용해 구조체의 시작 주소를 찾는 역할을 하는데 compound statment expression 이 사용됩니다.

// 0 을 (type *) 으로 캐스팅해서 type 구조체가 주소 0 에서 시작한다고 보면
// &((type *)0)->member 주소값은 해당 member 의 offset 값과 같게 됩니다.
#define offsetof(type, member) ((size_t) &((type *)0)->member)

// 인수로 전달된 실제 멤버 주소값에서 offsetof 값을 빼면 구조체의 시작 주소가 됩니다.
#define container_of(ptr, type, member) ({                        \
    void *__mptr = (void *)(ptr);                                 \
    ((type *)(__mptr - offsetof(type, member))); })

--------------------------------------------------------------

$ gcc -xc - <<\@ && ./a.out
#include <stdio.h>
#define offsetof(type, member) ((size_t) &((type *)0)->member)
#define container_of(ptr, type, member) ({ \
    void *__mptr = (void *)(ptr); \
    ((type *)(__mptr - offsetof(type, member))); })

struct mytype {
    char foo;
    int bar;
} con;

int main()
{
    printf("%p\n", &con);
    printf("%p\n", container_of(&con.bar, struct mytype, bar));
    return 0;
}
@

0x55ae96ca6018       # 실제 구조체 시작 주소와
0x55ae96ca6018       # container_of 로 구한 시작 주소가 같다.

do { . . . } while (0) 의 사용

보통 function-like 매크로는 확장이 완료되었을 때 statement 로 끝난다는 것을 나타내기 위해 매크로 사용시 뒤에 ; 를 붙입니다. 그런데 이것은 매크로 정의가 { ... } 를 사용하는 compound statement 로 이루어져 있을 경우 문제가 될 수 있습니다.

#define SKIP_SPACES(p, limit)  \
{ char *lim = (limit);         \
  while (p < lim) {            \
    if (*p++ != ' ') {         \
      p--; break; }}}

if (*p != 0)
    SKIP_SPACES (p, lim);       // 매크로 사용
else ...

-------------------------

if (*p != 0)                    // 매크로 확장 후에는
    { ................ };       // } 와 else 사이에 ; 가 오게되어 문법상 오류가 된다. 
else ...

따라서 이때는 매크로 정의를 반복이 발생하지 않는 do { ... } while(0) 문으로 감싸면 compound statement 가 하나의 statement 로 되므로 오류가 되지 않게 됩니다.

#define SKIP_SPACES(p, limit)     \
do { char *lim = (limit);         \
     while (p < lim) {            \
       if (*p++ != ' ') {         \
         p--; break; }}}          \
while (0)

if (*p != 0)
    do { ................ } while(0);    // 하나의 statement 가 되므로 오류가 되지 않는다.
else ...
// linux/include/linux/kernel.h 참조

#define swap(a, b) \
    do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)

만약에 확장결과가 statement 가 아니라 expression 이 되게 하려면 compound statement expression 을 사용하면 됩니다.

#define kfifo_len(fifo) \
({ \
    typeof((fifo) + 1) __tmpl = (fifo); \
    __tmpl->kfifo.in - __tmpl->kfifo.out; \
})

int chars = kfifo_len( &port->write_fifo);             // 오류가 되지 않는다.
. . .
while ( kfifo_len( &line6->messages.fifo) == 0) {      // 오류가 되지 않는다.
    mutex_unlock( &line6->messages.read_lock);
    . . .

함수의 연결

확장 결과에 따라 함수에 함수가 연이어 연결될 수 있습니다. 다음의 경우 concat (foo, bar) 매크로가 확장되면 결과가 foobar 가 되는데 foobar (100) 는 다시 foobar(a) 매크로와 매칭이 되므로 연이어 확장이 일어나게 됩니다.

$ gcpp
#define concat(a, b) a ## b
#define foobar(a) a + 1

concat (foo, bar) (100)         //  --->  foobar (100)  --->  100 + 1
@

100 + 1

이것을 활용하면 다음과 같이 if ~ else 를 만들 수도 있습니다.

$ gcpp
#define IF_1(true, ...) true
#define IF_0(true, ...) __VA_ARGS__
#define IF(value) IF_ ## value

// 인수가 2 개일 경우: 참일 경우는 foo 가 반환되고 거짓일 경우는 bar 가 반환된다.
IF(1) ( foo, bar )      // IF(1) --> IF_1 가 되고  IF_1 ( foo, bar ) --> foo
IF(0) ( foo, bar )      // IF(0) --> IF_0 가 되고  IF_0 ( foo, bar ) --> bar

// 인수가 1 개일 경우: 참일 경우 foo 가 반환되고 거짓일 경우는 empty 가 된다.
// 매크로의 매개변수 구성이 foo(a, b) 일때 인수가 1 개만 전달되면 오류가 되지만 
// ... 를 사용하는 variadic macro 의 경우는 오류가 되지 않습니다.
IF(1) ( foo )           // IF(1) --> IF_1 가 되고 IF_1 ( foo ) --> foo
IF(0) ( foo )           // IF(0) --> IF_0 가 되고 IF_0 ( foo ) --> empty
@

foo
bar

foo
       # empty

같은 이름의 object-like 매크로를 함께 정의할 수 없습니다.

따라서 다음의 경우 foo(); 는 매크로 확장이 되고 funcptr = foo 에서의 foo 는 그대로 남아 extern void foo(void) 함수의 포인터가 됩니다.

extern void foo(void);
#define foo() ... /* optimized inline version */
. . .
    foo();
    funcptr = foo;

함수명과 동일한 이름을 사용하는 매크로

다음과 같이 함수명과 동일한 이름의 매크로를 정의해 사용할 수 있습니다.

$ gcc -xc - <<\@ && ./a.out
#include <stdio.h>
int sum(int a, int b) {             // sum(a, b) 함수
    return a + b;
}
#define sum(a) sum(a, 10)           // sum(a) 매크로
int main() {
    printf("%d\n", sum(1));    
    return 0;
}
@

11

이때 만약에 원본 함수를 호출하려고 하면 전처리가 먼저 실행되기 때문에 오류가 발생하는데요.

$ gcc -xc - <<\@ && ./a.out
#include <stdio.h>
int sum(int a, int b) {
    return a + b;
}
#define sum(a) sum(a, 10)
int main() {
    printf("%d\n", sum(1, 2));        // 원본 함수를 호출
    return 0;
}
@

<stdin>: In function ‘main’:
<stdin>:7:27: error: macro "sum" passed 2 arguments, but takes just 1
<stdin>:5: note: macro "sum" defined here
. . .

이때는 다음과 같이 함수명에 괄호를 사용하면 오류 없이 원본 함수를 호출할 수 있습니다.

$ gcc -xc - <<\@ && ./a.out
#include <stdio.h>
int sum(int a, int b) {
    return a + b;
}
#define sum(a) sum(a, 10)
int main() {
    printf("%d\n", (sum)(1, 2));       // 함수명에 괄호를 사용하면
    return 0;                          // 매크로 확장을 방지할 수 있다.
}
@

3
-------------------------------

$ gcc -xc - <<\@ && ./a.out
#include <stdio.h>
#define sum(a, b) (a + b + 100)
int (sum)(int a, int b) {
    return a + b + 200;
}
int main() {
    printf("%d\n", sum(1, 2));        // 매크로 호출
    printf("%d\n", (sum)(1, 2));
#undef sum
    printf("%d\n", sum(1, 2));
    return 0;
}
@
103
203
203

Quiz

다음은 pthread_cleanup_push() 함수를 이용해 cleanup 함수를 등록하고 pthread_cancel() 함수를 실행했을 때 정상적으로 cleanup 함수가 호출되는지 알아보기 위한 테스트입니다. 그런데 컴파일 시부터 int main() 앞에 while 문이 없다는 이상한 오류가 발생하고 있는데요. 어떻게 하면 오류를 수정할 수 있을까요?

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void cleanup (void *arg)
{
    puts (">>> clanup function start");
    puts (">>> free()");
    free (arg);
    puts (">>> clanup function end");
}

void *runfunc (void *arg)
{
    char *mydata = (char *) malloc (1024);   

    pthread_cleanup_push (cleanup, (void *) mydata); 

    int count = 0;
    while (1)
    {
        printf ("Thread Running... : data=%d\n", count++);
        sleep(1);
    }
}

int main()
{
    pthread_t thread;
    int *retval;

    pthread_create (&thread, NULL, runfunc, NULL);
    sleep(5);
    puts ("sending cancellation request...");
    pthread_cancel (thread);
    pthread_join (thread, (void **) &retval);
    if (PTHREAD_CANCELED == (void *) retval)
        puts ("Thread cancellation completed");

    return 0;
}
--------------------------------------------------

$ gcc -pthread pthread_clean.c 
pthread_clean.c: In function ‘runfunc’:
pthread_clean.c:30:1: error: expected ‘while’ before ‘int’
   30 | int main()
      | ^~~
pthread_clean.c:44:1: error: expected declaration or statement at end of input
   44 | }
      | ^

다음과 같이 pthread_cleanup_push()pthread_cleanup_pop() 함수를 조회해보면 일반 함수가 아니라 function-like 매크로로 구성되어 있는 것을 알 수 있습니다. pthread_cleanup_push() 매크로의 경우는 do { 로 끝이 나고 pthread_cleanup_pop() 매크로는 do { } while (0);} while (0); 로 시작을 합니다. 따라서 두 매크로는 기본적으로 쌍으로 같이 사용하게끔 되어있습니다.

$ header-macro pthread.h -s pthread_cleanup

#define pthread_cleanup_push(routine,arg) do { 
    __pthread_unwind_buf_t __cancel_buf; 
    void (*__cancel_routine) (void *) = (routine); 
    void *__cancel_arg = (arg); 
    int __not_first_call = __sigsetjmp ((struct __jmp_buf_tag *) (void *) __cancel_buf.__cance>
    if (__glibc_unlikely (__not_first_call)) { 
        __cancel_routine (__cancel_arg); 
        __pthread_unwind_next (&__cancel_buf); 
    } 
    __pthread_register_cancel (&__cancel_buf); 
    do {


            // 이 위치에 사용자 코드가 오게됩니다.


#define pthread_cleanup_pop(execute) do { } while (0); 

    } while (0); 
    __pthread_unregister_cancel (&__cancel_buf); 
    if (execute) __cancel_routine (__cancel_arg); 
} while (0)

따라서 다음과 같이 pthread_cleanup_push() 매크로와 pthread_cleanup_pop() 매크로 사이에 사용자 코드가 오게끔 매크로를 위치시키면 오류가 발생하지 않게 됩니다. 이때 주의할 것은 pthread_cleanup_pop() 매크로가 while 문의 중괄호 안에 위치한다던가 하면 안되겠죠. 괄호의 발란스가 맞아야 합니다.

void *runfunc (void *arg)
{
    char *mydata = (char *) malloc (1024);

    pthread_cleanup_push (cleanup, (void *)mydata); 

    int count = 0;
    while (1)
    {
        printf("Thread Running... : data=%d\n", count++);
        sleep(1);
    }

    pthread_cleanup_pop (1);    // 매크로 추가
}

------------------------------------------------

$ gcc -pthread test.c 

$ ./a.out 
Thread Running... : data=0
Thread Running... : data=1
Thread Running... : data=2
Thread Running... : data=3
Thread Running... : data=4
sending cancellation request...
>>> clanup function start
>>> free()
>>> clanup function end
Thread cancellation completed

pthread_cleanup_push() 매크로로 등록한 cleanup 함수가 호출되는 경우는

  1. 위에서처럼 pthread_cancel() 함수에 의해 종료될 때
  2. 스레드가 pthread_exit() 으로 종료될 때 ( return 으로 종료될 경우는 제외 ).
  3. pthread_cleanup_pop(1) 매크로를 만날 경우도 호출됩니다 ( 이때 인수 값이 0 이면 제외 ).

pthread_detach() 한 스레드의 경우는 pthread_cancel() 함수에 의해 종료될 때 cleanup 함수가 호출되지 않습니다. 그리고 스레드를 detach 하거나 join 하면 스레드가 종료될 때 해당 스레드가 사용했던 stack 이나 thread local storage 같은 리소스가 자동으로 반환되지만 사용자가 직접 malloc 으로 할당한 메모리는 반환되지 않습니다. 직접 free 로 해제해 주어야 합니다.

2 .

C 언어에서 generic 한 함수를 만들려면 어떻게 할까요? 가령 함수 이름이 foo(x) 인데 x 값으로 float 을 주면 float 을 처리하는 함수가 실행되고, int 값을 주면 int 를 처리하는 함수가 실행됩니다.

_Generic 키워드를 사용하면 C 언어 에서도 generic 함수를 만들 수 있습니다.

#include <stdio.h>

// x 값의 타입이 float 일 경우 _Generic((x), ... ) 부분이 foo_float 으로 치환되는 결과가 된다.
// (":" 왼쪽에는 lvalue conversion 된 타입이 오고 오른쪽엔 expression 이 옵니다.)
#define foo(x) _Generic((x), \
        float   : foo_float, \
        double  : foo_double , \
        default : foo_int )(x)

void foo_float(float x) { puts("this is float"); }
void foo_double(double x) { puts("this is double"); }
void foo_int(int x) { puts("this is int"); }

int main(int argc, char *argv[]) 
{
    foo(12.3f);
    foo(12.3);
    foo(123);
    return 0;
}

---------------------------------

$ gcc generic.c

$ ./a.out
this is float
this is double
this is int