Top > Blog > Article > 【C言語】ライブラリをリンクしなくてもpowが使える(最適化オプションについて調べた話)

【C言語】ライブラリをリンクしなくてもpowが使える(最適化オプションについて調べた話)

2022-08-07
2022-08-07

pow関数をご存知ですか?math.hをインクルードすることによって使えるようになる、第1引数の第2引数乗を得ることが出来る数学関数です。ちなみにC17では7.12.7.4に書いてあります。

さて、math.hといえば、ビルド時に -lm といったオプションを付けていなくて怒られた記憶がある人も多いのではないでしょうか。数学に関する関数を呼ぶ時は別途ライブラリのリンクが必要になるということですね。

このオプションを付けないと、こんな感じでリンクエラーになります。

$ bat a.c
#include <stdio.h>
#include <math.h>

int main(void) {
  int a;
  scanf("%d", &a);
  double b = pow(a, 2);
  printf("%lf\n", b);
  return 0;
}

$ gcc -g a.c
/usr/sbin/ld: /tmp/ccTfoaH6.o: in function `main':
a.c:(.text+0x54): undefined reference to `pow'
collect2: error: ld returned 1 exit status

さて。こんなことを言っておきながら、pow関数を -lm オプションなしで使うこともできます。今回はこの現象について書いていきます。

最適化オプション

タイトルでネタバレをしていますが、最適化オプションを付けることによって、問題なくビルドできます。

$ gcc -g -O a.c
(エラーはもちろん、警告も表示されない)

当然、最適化によってpow関数を呼び出す必要がなくなったということでしょう。

なぜリンクする必要がなくなったのか?

-O オプションを付けずにビルドした実行ファイルをobjdumpで覗いてみましょう。

$ gcc -g -lm b.c
$ objdump -Mintel -dS a.out
  double b = pow(a, 2);
    119b:       8b 45 ec                mov    eax,DWORD PTR [rbp-0x14]
    119e:       66 0f ef d2             pxor   xmm2,xmm2
    11a2:       f2 0f 2a d0             cvtsi2sd xmm2,eax
    11a6:       66 48 0f 7e d0          movq   rax,xmm2
    11ab:       f2 0f 10 05 5d 0e 00    movsd  xmm0,QWORD PTR [rip+0xe5d]        # 2010 <_IO_stdin_used+0x10>
    11b2:       00
    11b3:       66 0f 28 c8             movapd xmm1,xmm0
    11b7:       66 48 0f 6e c0          movq   xmm0,rax
    11bc:       e8 6f fe ff ff          call   1030 <pow@plt>
    11c1:       66 48 0f 7e c0          movq   rax,xmm0
    11c6:       48 89 45 f0             mov    QWORD PTR [rbp-0x10],rax

11bcでpow@pltを読んでいますね。

次に、 -O オプションを付けてみましょう。

  double b = pow(a, 2);
    117e:       66 0f ef c0             pxor   xmm0,xmm0
    1182:       f2 0f 2a 44 24 04       cvtsi2sd xmm0,DWORD PTR [rsp+0x4]
    1188:       f2 0f 59 c0             mulsd  xmm0,xmm0

[rsp+0x4] 、すなわち変数aの値を cvtsi2sd (ConVerT Signed-Int to Signed-Double?) で xmm0 に入れ、xmm0 と xmm0 を掛け算しています。つまりa*aですね。まごうことなきpow(a, 2) です。

つまり、最適化オプションによって、pow(a, 2) は a*a に展開されることがわかりました。

-Oオプションは何をする?

gccのドキュメントを読んでみましょう。

[OGP image not found]

以下の最適化フラグが有効になるらしいです。

-fauto-inc-dec
-fbranch-count-reg
-fcombine-stack-adjustments
-fcompare-elim
-fcprop-registers
-fdce
-fdefer-pop
-fdelayed-branch
-fdse
-fforward-propagate
-fguess-branch-probability
-fif-conversion
-fif-conversion2
-finline-functions-called-once
-fipa-modref
-fipa-profile
-fipa-pure-const
-fipa-reference
-fipa-reference-addressable
-fmerge-constants
-fmove-loop-invariants
-fmove-loop-stores
-fomit-frame-pointer
-freorder-blocks
-fshrink-wrap
-fshrink-wrap-separate
-fsplit-wide-types
-fssa-backprop
-fssa-phiopt
-ftree-bit-ccp
-ftree-ccp
-ftree-ch
-ftree-coalesce-vars
-ftree-copy-prop
-ftree-dce
-ftree-dominator-opts
-ftree-dse
-ftree-forwprop
-ftree-fre
-ftree-phiprop
-ftree-pta
-ftree-scev-cprop
-ftree-sink
-ftree-slsr
-ftree-sra
-ftree-ter
-funit-at-a-time

それでは、-Oで有効になるフラグのうち、pow関数の展開に働きかけるフラグはどれなのか調べてみようと思っていました。

これらをすべて指定してビルドしてみます。

$ gcc -g -fauto-inc-dec -fbranch-count-reg -fcombine-stack-adjustments -fcompare-elim -fcprop-registers -fdce -fdefer-pop -fdelayed-branch -fdse -fforward-propagate -fguess-branch-probability -fif-conversion -fif-conversion2 -finline-functions-called-once -fipa-modref -fipa-profile -fipa-pure-const -fipa-reference -fipa-reference-addressable -fmerge-constants -fmove-loop-invariants -fmove-loop-stores -fomit-frame-pointer -freorder-blocks -fshrink-wrap -fshrink-wrap-separate -fsplit-wide-types -fssa-backprop -fssa-phiopt -ftree-bit-ccp -ftree-ccp -ftree-ch -ftree-coalesce-vars -ftree-copy-prop -ftree-dce -ftree-dominator-opts -ftree-dse -ftree-forwprop -ftree-fre -ftree-phiprop -ftree-pta -ftree-scev-cprop -ftree-sink -ftree-slsr -ftree-sra -ftree-ter -funit-at-a-time b.c

すると、なんとエラーになってしまいました。

cc1: warning: this target machine does not have delayed branches
/usr/sbin/ld: /tmp/ccQTAQOO.o: in function `main':
/home/watasuke/program/c/b.c:7: undefined reference to `pow'
collect2: error: ld returned 1 exit status

は~?

その他オプションはそのまま、 -c でコンパイルだけしてobjdumpしてみます(コマンド省略)。

  double b = pow(a, 2);
  30:   f2 0f 10 05 00 00 00    movsd  xmm0,QWORD PTR [rip+0x0]        # 38 <main+0x38>
  37:   00
  38:   66 0f ef d2             pxor   xmm2,xmm2
  3c:   f2 0f 2a 54 24 0c       cvtsi2sd xmm2,DWORD PTR [rsp+0xc]
  42:   66 48 0f 7e d0          movq   rax,xmm2
  47:   66 0f 28 c8             movapd xmm1,xmm0
  4b:   66 0f 28 c2             movapd xmm0,xmm2
  4f:   e8 00 00 00 00          call   54 <main+0x54>
  54:   66 48 0f 7e c0          movq   rax,xmm0
  59:   f2 0f 11 44 24 10       movsd  QWORD PTR [rsp+0x10],xmm0

全然展開されてないですね・・・。

すべての最適化がフラグによって管理されているわけではない

さっきのgccドキュメントに戻ってもう一回読んでみましょう。

Not all optimizations are controlled directly by a flag. Only optimizations that have a flag are listed in this section.

ふーん

どうやら、すべての最適化がフラグによって管理されているわけではないらしいです。-O1 オプションでのみ有効になる最適化によってpow関数が展開されているのでしょうか・・・。

おわり

調べてみましたが、よくわかりませんでした。 いかがでしたか!??!?!?

久しぶりに微妙なブログを書いてしまった気がします。Scrapboxでよかったかも。

icon

わたすけ

高専生 プログラミングでツールを作ったりLinux触ったりゲームしたりしてる
プロフィール詳細はこちら

タグ
最適化オプションなぜリンクする必要がなくなったのか?-Oオプションは何をする?すべての最適化がフラグによって管理されているわけではないおわり