C++ 언어는 그 특성상, 전역 개체의 초기화가 main 보다 먼저 이루어지므로 전역 개체의 생성자에 들어 있는 코드는 main 보다 먼저 호출된다.
이것은 여러가지 트릭으로 사용될 수 있는데, C에는 과연 그런 것이 없을까? 표준 명세에는 없다.
하지만, gcc의 __attribute__에는 그러한 일을 가능하게 해주는 지시자가 있는데, 바로 다음과 같이 사용된다.

void __attribute__((constructor)) before_main( void )
{
/* Things to do before main function */
}


또한 main 뒤에 호출되는 전형적인 방식은 atexit에 등록하는 것인데, 이것또한

void __attribute__((destructor)) after_main( void )
{
/* Things to do after main function */
}


와 같은 방식으로 호출된다.

간단한 샘플을 돌려보자면,
$ more c.c
#ifdef STRIP_ATTR
#define __attribute__(x)
#endif
void __attribute__((constructor)) before_main( void )
{
printf("I miss you Lorthlorien ever beauty.\n");
}

void __attribute__((constructor)) before_main_2nd( void )
{
printf("Bombadil, where have you been in the morning?\n");
}

void __attribute__((destructor)) after_main( void )
{
printf("Mithlandir, help me!\n");
}

int main()
{
printf("I am working, no touch!\n");
return 0;
}

$ gcc -o c1 -save-temps c.c
$ ./c1
Bombadil, where have you been in the morning?
I miss you Lorthlorien ever beauty.
I am working, no touch!
Mithlandir, help me!



근사하지 않은가? 다만 의심가는 것은 __attribute((constructor))로 지정한 함수들이 두 개 이상일때의 순서는 어떠하냐는 것인데, 외관상 stack형식으로 먼저 발견되는 것이 나중에 실행되는 것 같다.
자세한 것은 추후에 설명하지도 모르겠다.

이것이 수행되는 원리를 좀더 파헤쳐보자면,

우선 위 코드상에 STRIP_ATTR이라고 되어 있는 부분이 수행되도록하면 평범한 함수가 되는데, 그렇게 생성된 바이너리(c2)의 심볼들을 살펴보자.
nm 은 -n 옵션을 주어 번지로 정렬되도록 하였다.
두 nm 결과를 비교하는데는 unified diff를 사용하여 보자.
그리고 -save-temps 를 통해 생성되는 c.s 라는 어셈블리어 파일은 각각 c1.s c2.s로 이름을 바꾸는 과정이 들어가 있다.

$ mv c.s c1.s
$ gcc -o c1 c.c
$ gcc -o c2 -DSTRIP_ATTR -save-temps c.c
$ mv c.s c2.s
$ nm -n c1 > c1.nm
$ nm -n c2 > c2.nm
$ diff -u c1.nm c2.nm
--- c1.nm Thu Jan 5 13:29:16 2006
+++ c2.nm Thu Jan 5 13:29:20 2006
@@ -27,14 +27,14 @@
0804952c r __FRAME_END__
08049530 D _DYNAMIC
080495f8 d __CTOR_LIST__
-08049604 d __CTOR_END__
-08049608 d __DTOR_LIST__
-08049610 d __DTOR_END__
-08049614 d __JCR_END__
-08049614 d __JCR_LIST__
-08049618 D _GLOBAL_OFFSET_TABLE_
-08049630 A __bss_start
-08049630 A _edata
-08049630 b completed.1
-08049634 b object.2
-0804964c A _end
+080495fc d __CTOR_END__
+08049600 d __DTOR_LIST__
+08049604 d __DTOR_END__
+08049608 d __JCR_END__
+08049608 d __JCR_LIST__
+0804960c D _GLOBAL_OFFSET_TABLE_
+08049624 A __bss_start
+08049624 A _edata
+08049624 b completed.1
+08049628 b object.2
+08049640 A _end


이것을 잘 보아하니 __CTOR_LIST__ 라는 값까지는 같고 그 뒤가 달라지는 것을 볼 수가 있다.
아! __CTOR_LIST__에 내용이 채워지면서 조금씩 그 뒤로 밀려나는구나!

그럼 이 둘의 어셈블코드의 차이는 어떠할까?

$ diff -u c1.s c2.s
--- c1.s Thu Jan 5 13:31:04 2006
+++ c2.s Thu Jan 5 13:31:13 2006
@@ -15,9 +15,6 @@
leave
ret
.size before_main, .-before_main
- .section .ctors,"aw",@progbits
- .align 4
- .long before_main
.section .rodata
.align 32
.LC1:
@@ -34,9 +31,6 @@
leave
ret
.size before_main_2nd, .-before_main_2nd
- .section .ctors
- .align 4
- .long before_main_2nd
.section .rodata
.LC2:
.string "Mithlandir, help me!\n"
@@ -52,9 +46,6 @@
leave
ret
.size after_main, .-after_main
- .section .dtors,"aw",@progbits
- .align 4
- .long after_main
.section .rodata
.LC3:
.string "I am working, no touch!\n"


아앗! 이것은!
단지 section .ctor, .dtor에 함수 포인터만 추가하는 일을 하는 것 아닌가.
그렇다면, .ctor를 찾아서 하나씩 호출하는 부분은 어디에 있다는 것이지? ((계속))
  1. 2006.01.06 10:38

    이걸로 얻을 수 있는 이득은 뭘까?

  2. 주인 2006.01.10 00:07

    전역개체에 malloc 등이 필요한 초기화가 있을 경우 유용하지.

  3. 하~~ 2006.01.25 16:02

    멋지십니다...
    자주 들어와서 좋은 정보 얻어가겠습니다..

  4. 주인 2006.01.26 10:24

    뉘신지 모르겠지오마는 자주 오시겠다니 감사합니다. :)

  5. NoSyu 2006.08.11 17:08

    반갑습니다. 미친감자씨 블로그를 타고 왔습니다.
    C에도 이런 기법이 있었다는 걸 몰랐습니다.
    (정확히는 GCC 확장이겠지만..)
    그런데 사진을 보니 어디서 많이 보신 분 같은데..
    혹시 KLDP에서 활동하시는지....

    • 최호진 2006.08.11 17:26

      네, 맞습니다. drupal 이전 phpbb 시절에 관리 좀 도왔었지요.

+ Recent posts