LoginSignup
84
26

More than 5 years have passed since last update.

整数を419378回インクリメントするとMacのg++が死ぬ

Last updated at Posted at 2018-02-23

はじめに

C++でアスタリスクをつけすぎると端末が落ちるという記事を書いたら、@tanakhさんから

というコメントがありまして。

なるほど確かにソースコードのサイズはアスタリスクの数の二乗に比例するので、コンパイル時間とソースコードのサイズの関係は線形だなぁ、と。

ただ、内部で型のテーブルとか作ってると思うんで1、これが単純にソースコードのサイズの問題なのか調べるために、今度はもう少し簡単なコードを書いてみて、コンパイル時間やコンパイラが利用するメモリを調べてみようと思ったわけですよ。

今回のトリビアの種

こんなのを書きました。

gen.rb
def generate(n)
  open("test.cpp","w") do |f|
    f.puts <<EOS
#include <cstdio>
int
main(void){
  int i=0;
EOS
    n.times do
      f.puts "  i++;"
    end
    f.puts <<EOS
  printf("%d\\n",i);
}
EOS
  end
end

n = 100
n = ARGV[0].to_i if ARGV.size > 0
generate(n)

これは、単にi++;を並べるだけのコードです。

$ ruby gen.rb 5 

とかすると、

test.cpp
#include <cstdio>
int
main(void){
  int i=0;
  i++;
  i++;
  i++;
  i++;
  i++;
  printf("%d\n",i);
}

が出力されます。数字を増やすとi++;の行数が増えていきます。これを、以前の記事と同じくらいのサイズになるように調整して、コンパイラの負荷を調べてみようと思ったわけです。

まず10万行くらいから行きますかね。

$ ruby gen.rb 100000
$ wc test.cpp
  100006  100008  700067 test.cpp
$ gtime -f "%e %M" g++ test.cpp
2.53 205680

楽勝ですね。じゃ、50万行。

$ ruby gen.rb 500000 
$ gtime -f "%e %M" g++ test.cpp
g++: internal compiler error: Segmentation fault: 11 (program cc1plus)
Please submit a full bug report,
with preprocessed source if appropriate.
See <https://github.com/Homebrew/homebrew-core/issues> for instructions.
Command exited with non-zero status 4
3.47 515904

・・・ん?なんかメモリを使い切る前に変な死に方したぞ。

「10万行では死なず、50万行では死にました。ということは、どこかにぎりぎり死ぬ行数があるはずですね。これってトリビアになりませんかね。」

このトリビアの種、つまりこういうことになります。

「整数をXXXXXX回インクリメントするコードを食わせると、コンパイラが死ぬ」

実際に調べてみた。

調査コード

どこで死ぬかを二分探索するわけだが、面倒なのでスクリプトにやらせよう。手抜きだが、こんな感じのスクリプトになると思う。

increments.rb
def generate(n)
  open("test.cpp","w") do |f|
    f.puts <<EOS
#include <cstdio>
int
main(void){
  int i=0;
EOS
    n.times do
      f.puts "  i++;"
    end
    f.puts <<EOS
  printf("%d\\n",i);
}
EOS
  end
end

def search
  s = 100000
  e = 500000
  while (e != s && e != s+1)
    n = (e+s)/2
    generate(n)
    if system("g++ test.cpp 2> /dev/null")
      puts "#{n} OK"
      s = n
    else
      puts "#{n} NG"
      e = n
    end
  end
end

search

単純に10万行から50万行の間を二分探索していくコード。コンパイルが成功したかはsystemの返り値で判定している。

さっそく実行してみよう。実行環境は前回と同じでこんな感じ。

  • 測定環境
    • MacOS X High Sierra
    • プロセッサ 3.3 GHz Intel Core i5
    • メモリ 8GB
    • g++ (Homebrew GCC 7.2.0) 7.2.0
$ ruby increments.rb
300000 OK
400000 OK
450000 NG
425000 NG
412500 OK
418750 OK
421875 NG
420312 NG
419531 NG
419140 OK
419335 OK
419433 NG
419384 NG
419359 OK
419371 OK
419377 OK
419380 NG
419378 NG

というわけで、419377回インクリメントするコードならコンパイルできて、419378回インクリメントするコードを食わすとg++が死ぬ。

  • 他に調べたこと
    • メモリが半分の4GBしか積んでないMac Book Proで試しても同じ結果になったので、メモリの問題ではない。
    • Linuxでは同じバージョンのGCCでもエラーにならない(追記:より大きなサイズではUbuntuでも死んだそうです)
    • インテルコンパイラも大丈夫
    • clang++ではMacでもエラーにならない

Windowsは調べてない。

まとめ

こうしてこの世界にまた一つ
新たなトリビアが生まれた2

整数を419378回3インクリメントするコードを食わせると、g++ (Homebrew GCC 7.2.0)が死ぬ。


  1. いや、コンパイラ屋さんじゃないのでよく知らんけど。 

  2. えーと、もともとはソースコードサイズとコンパイル時間の関係を調べようかと思ってたんだっけ。まぁいいや。気になったら誰かやって。 

  3. しかし419378って数字、中途半端だな。なんで決まってるんだろ? 

84
26
9

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
84
26