元より野次馬、このところgccのオプションの「なんだかわからん」「不穏な雰囲気がする」やつらをめぐっております。今回は -fno-builtinオプションとな。コンパイラ素人の勝手な理解では、gccが「内蔵する」ビルドイン関数を使わないでくれとリクエストするためのオプション、である筈。しかしこの使い方が分からないデス。
※『オプション沼』投稿順indexはこちら
※今回は動作確認に以下の2つを使用しています
-
- Windows11 WSL2上のUbuntu 20.04 LTS、gccのバージョンは9.4.0
- Raspberry Pi 4 model B上のRaspberry Pi OS(64bit)、gccのバージョンは10.2.1
-fno-builtin オプション
今回実験してみた -fno-builtinオプションは、以下の公式ドキュメントをたどっていくとしっかり記述されております。
3.4 Options Controlling C Dialect
読解力のない、このコンパイラ素人が読み取ったところでは、GCCの場合、いくつかの関数はライブラリコール等をせず、コンパイラが生成する「ビルトイン」関数(展開されてしまうような書きぶりなのでインライン関数みたいな感じ?)が使用されるみたいっす。しかし、この -fno-builtin オプションをつけてコンパイルすると、そのビルトイン関数の使用が抑制される、つまりはライブラリ側の関数が呼び出されると。こんな理解で本当にいいのか?
ビルトインに展開されてしまう関数として候補があれこれ書かれていた中で、以下の実験用ソース2つを作成してみました。
第1のソース t0.c
SINとかCOSとか三角関数などもビルトインの候補になるようだったので、第1はその実験用ソースです。ぶっちゃけx86_64では機械語レベルでSIN/COS命令があり、Arm A64には無し。さすればx86_64とArmで差があるのではないかという下衆の勘ぐりデス。
#include <stdio.h> #include <stdlib.h> #include <math.h> #define CPI (3.14159265359) int main(int argc, char const *argv[]) { float angle; float xsin; if (argc < 2) { exit(1); } angle = (float)atoi(argv[1]) * CPI / 180.0f; xsin = sin(angle); printf("sin(%f[rad])= %f\r\n",angle, xsin); return 0; }
第2のソース t1.c
第2は、オプションのドキュメント読んでいるとビルトイン関数として処理されるようなことが書かれている、alloca、memcpyを使ってみるソースです。わざわざ説明あり、allocaは単なるスタック調整になったりすることもある、というような書きぶりデス。きっと -fno-builtinをつけたりつけなかったりすると生成されるコードが変わるんでないかと期待。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <alloca.h> int main(int argc, char const *argv[]) { char *temp; if (argc < 2) { exit(1); } temp = alloca(64); memcpy(temp, argv[1], 3); temp[0] = '$'; temp[3] = '\0'; printf("%s => %s\r\n",argv[1], temp); return 0; }
実験の手順
t0.c の方は以下のようです。-fno-builtin オプション無と有りの2つでオブジェクトコードを作り、作成された実行ファイルをobjdumpでディスアセンブルし、その結果をファイルコンペアするというもの。t0.cの方はMATHつかっているので-lmでリンク指定してます。
$ gcc -o t0 t0.c -lm $ gcc -fno-builtin -o t0n t0.c -lm $ objdump -d t0 >t0.lis $ objdump -d t0n >t0n.lis
t1.c の方は以下のようです。上のt0.cで-lmしているところがちょっと引っかかります。t1.cの方は、そういうアカラサマなライブラリとのリンクはなしとしたかったです。やってることはほぼほぼ同じです。
$ gcc -o t1 t1.c $ gcc -fno-builtin -o t1n t1.c $ objdump -d t1 >t1.lis $ objdump -d t1n >t1n.lis
勿論、念のため、実行ファイルを作ったら動作確認してます。こんな感じ。動作はOKよな。
実験結果
t0.cとt1.c、x86_64とA64の両方でビルドしましたが結果は同じでした。
-fno-builtin つけようが付けまいが出来たオブエクトに差がない
おっと。これは想定外ってやつ。何が悪かったの? -fno-builtin以外のオプションと絡めないとダメなの? 前回やった不穏な -ansi とか、-O3 で最適化とか、いろいろ組み合わせを試してみましたが反応なし。-fno-builtinをつけようとつけまいとできたオブジェクトに変りなし。
無理やりビルトイン関数を使ってみる
ビルトイン関数そのものの存在を疑い、t1.cのalloca呼び出しを以下のように変更してみました。alloca()とするのでなく、アカラサマに__builtin_alloca()を呼び出し。
上記のように変更してみたらば、出来上がったオブジェクトコード見事に変わりました。従前のalloca()のときとはまったく異なりますです(疲れたので生成されたコードをよく見てないけど。)
ビルトイン関数はちゃんと存在するようですな。それにしても、-fno-builtinしなくてもalloca()のときは__builtin_allocaは使用されてないみたい。
なお、alloca()という名の関数は alloca.h 内で以下のように定義されているマクロの筈です。あれあれ、こちらも実体は__builtin_alloca()じゃん。
なして、同じコードが生成されない。マクロが有効でない?簡単と思ったオプションになにやら不穏な空気がただよう。なんかの勘違い?