オプション沼(20) gcc、-Warray-bounds、最適化のときは世話焼きになる

Joseph Halfmoon

警告オプションの重箱の隅をつつくようなことをしていると、「これ駄目でしょ」的なコードを書くことになります。しかし何の警告も出ずにコンパイル出来、ましてや実行もできてしまうと。どう見たってエラーなんだけれども。今回のWarray-boundsオプションは、最適化するとビシビシ指摘してくれるのに、最適化しないとザル?

※『オプション沼』投稿順indexはこちら

※今回は動作確認に以下を使用しています

    • Windows11 WSL2上のUbuntu 20.04 LTS、gccのバージョンは9.4.0
-Warray-boundsオプション

今回のオプションは、配列の範囲を逸脱する危険があるような場合にコンパイル時に警告を出してくれるオプションです。そのようなチェックを専門に行ってくれるアドレス・サニタライザという仕組みもあるのですが、このオプションはそれとは独立みたいです。詳細については以下の御本家ドキュメントをご覧くだせーまし。

3.8 Options to Request or Suppress Warnings

この -Warray-bounds というオプションは -Wall で自動的にONになるオプションなので -Wall しておけば一応アクティブになっているのですが、はっきり言って

最適化かけてないときはザル

です。アカラサマに酷いコードを書いているのに警告だしてくれません。しかし、最適化

-O2

をかけると突如として頑張って警告を出してくれます。これは -O2 オプションをつけるとONになるコマケー最適化オプションの一つ、-ftree-vrp がアクティブになるための活性化みたいです。これは、配列の境界チェックやnullポインタチェックなどの「範囲チェックを削除」する目的でレンジチェックをやるときに、逆に危ないところも見つけるようになる、ということみたいっす。知らんけど。

今回実験のC言語ソース

今回のソースでは「よゐこ」はやってはイケない酷い書き方してます。以下のコードを見ると、コンパイルできても実行したらセグメンテーション・フォルトとか言われて一巻を終わり、と思ったのです。しかし、当方実行環境では、どこふく風で実行可能。エラーも発生せずです。動いちゃうのね。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ARRAYSIZE   (5)

int ary[ARRAYSIZE];
int ary2[ARRAYSIZE];

void setAry(int* array, int vl) {
    int idx = 0;
    while (idx < ARRAYSIZE) {
        array[idx++] = vl;
    }
}

void incElem(int* arg) {
    arg++;
    *arg += 1;
}

void printAry(char* nam, int* array) {
    int i;
    printf("%s : ", nam);
    for (i = 0; i<ARRAYSIZE; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");
}

int main(int argc, char const *argv[])
{
    setAry(ary, 1);
    setAry(ary2, 2);
    printf("ary[ARRAYSIZE]   = %d\n", ary[ARRAYSIZE]);
    printf("ary[ARRAYSIZE+1] = %d\n", ary[ARRAYSIZE+1]);

    incElem(ary);
    incElem(&ary[ARRAYSIZE]);
    ary[ARRAYSIZE] = 999;
    printAry("ARY", ary);
    printAry("ARY2", ary2);
    printf("ary[ARRAYSIZE]   = %d\n", ary[ARRAYSIZE]);
    printf("ary[ARRAYSIZE+1] = %d\n", ary[ARRAYSIZE+1]);

    return 0;
}
最適化かけずにビルドして実行の場合

上記コードを以下のようなコマンドラインでビルドしてみます。

O0build

-Warray-boundsオプションつけてますが、何の警告も出ません。オプションを警告全部載せの -Wall に変えても無警告です。

元のソースは配列の範囲外へアクセスしていたり、酷いことをやっているのですが、何の問題もなく実行可能でした。こんな感じ。O0Run

その理由は、生成されたオブジェクトコードにあります。確保したのは20バイトのサイズの配列ですが、32バイト毎に配置されています。つまり配列の後ろ12バイトほどの隙間があるようです。正確にいうと問題のaryという名の配列の直後はシンボル_endとなっていて、そこにも書き込んでしまっている筈なのですが、積極的に何かに使っている場所ではないので、メモリのアロケーション的には「問題ない」ってことみたい。O0symbol

 

O2最適かけてビルドして実行の場合

最適化かけないと上記のように、何もしてくれない-Warray-boundsオプションですが、-O2つけると急に働き始めます。こんな感じ。O2build

長かったので続きを別画像にしてます。こんな感じ。O2build2

warningだけでなく、いちいち note とかも吐いてくれて至れり尽くせり。

上記だと見難いので、問題を指摘された箇所をソース上にマーカ引いて示しました。WarningLocations

アカラサマにヤバイ奴らを一網打尽とな。しかし警告は警告、オブジェクトは生成されとりますし、上に示した理由で問題なく動作してしまうと。O2Run

最適化オプションで豹変する、警告オプションでした。

オプション沼(19) gcc、weak symbolの真偽、続-Waddressオプション へ戻る

オプション沼(21) gcc、-Wuninitialized、最適化のときだけ働く、その2 へ進む