2018年4月10日火曜日

C 言語の extern の挙動確認

『extern で外部関数定義をすると、「ポインタ」的なものが生成される』という話が出てきて、 んん?と思ったので gcc で挙動を確認する。

環境

下記環境で動作確認を行った。

  • OS: Windows10 Pro
  • MSYS2
  • gcc.exe (Rev1, Built by MSYS2 project) 7.2.0

多分 pacman -S base-devel で環境一式そろうはず...。

extern を使ったコードの作成

■ main.c

#include <stdio.h>

extern int impl_func();

void echo(char* str) {
    printf(str);
}

int main(int argc, char* argv[]) {

    echo("Hello, World!");

    impl_func();

    return 0;
}

■ impl.c

int impl_func() {
    return 1;
}

ビルド

コンパイル

$ gcc -c main.c -o main.o
$ gcc -c impl.c -o impl.o

リンク

$ ld -plugin C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.2.0/liblto_plugin-0.dll -plugin-opt=C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.2.0/lto-wrapper.exe -plugin-opt=-fresolution=C:\msys64\tmp\ccLHLsgf.res -plugin-opt=-pass-through=-lmingw32 -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lmoldname -plugin-opt=-pass-through=-lmingwex -plugin-opt=-pass-through=-lmsvcrt -plugin-opt=-pass-through=-lpthread -plugin-opt=-pass-through=-ladvapi32 -plugin-opt=-pass-through=-lshell32 -plugin-opt=-pass-through=-luser32 -plugin-opt=-pass-through=-lkernel32 -plugin-opt=-pass-through=-lmingw32 -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lmoldname -plugin-opt=-pass-through=-lmingwex -plugin-opt=-pass-through=-lmsvcrt -m i386pep -Bdynamic -o main.exe C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.2.0/../../../../x86_64-w64-mingw32/lib/../lib/crt2.o C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.2.0/crtbegin.o -LC:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.2.0 -LC:/msys64/mingw64/bin/../lib/gcc -LC:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.2.0/../../../../x86_64-w64-mingw32/lib/../lib -LC:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.2.0/../../../../lib -LC:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.2.0/../../../../x86_64-w64-mingw32/lib -LC:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.2.0/../../.. main.o impl.o -lmingw32 -lgcc -lgcc_eh -lmoldname -lmingwex -lmsvcrt -lpthread -ladvapi32 -lshell32 -luser32 -lkernel32 -lmingw32 -lgcc -lgcc_eh -lmoldname -lmingwex -lmsvcrt C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.2.0/../../../../x86_64-w64-mingw32/lib/../lib/default-manifest.o C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.2.0/crtend.o

比較

シンボルテーブル

特別なシンボルは増えていないようだ。

externimpl_func 関数を外部定義参照した main.o は、 シンボル impl_func が未定義となる。

リンク時点で未定義シンボルのアドレス解決が行われ、 main.o の時点では未定義だったアドレスが設定されるということらしい。

$ nm main.o
0000000000000000 b .bss
0000000000000000 d .data
0000000000000000 p .pdata
0000000000000000 r .rdata
0000000000000000 r .rdata$zzz
0000000000000000 t .text
0000000000000000 r .xdata
                 U __main
0000000000000000 T echo
                 U impl_func
000000000000001c T main
                 U printf

$ nm impl.o
0000000000000000 b .bss
0000000000000000 d .data
0000000000000000 p .pdata
0000000000000000 r .rdata$zzz
0000000000000000 t .text
0000000000000000 r .xdata
0000000000000000 T impl_func

$ nm main.exe | grep impl_func
00000000004015c0 T impl_func

うーん、これで extern の挙動分かったつもりになったけど、 把握するうえでこれ以外に情報は必要だろうか?

とりあえず調べた内容は残しておく。