質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.37%

strict aliasing ruleについて

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 2,148

maisumakun

ユーザーランキング総合1位

x64のWindows上でCプログラミングをしている途中に、mallocで確保してunsigned long(Windowsでは32ビット)の配列として値を詰め込んだ領域を、unsigned long longの配列として読み替えたくなる場面が出てきました。そこからいろいろ気になっての質問なのですが、

質問1:strict aliasing ruleについて調べてみたところ、互換性のない型で「書き込んだ」場合に未定義な動作となるというように読めるのですが、unsigned longで書き込んだものをunsigned long longとして読み取るだけでは未定義の動作とはならない、という解釈で間違いはないでしょうか(もちろん、最後に出る端数についてはunsigned long longアクセスしない前提です)。

質問2:質問1とは逆に、unsigned long longの配列として書き込んだ値をunsigned longの配列として読み書きしたいとします(もちろん、この2つのサイズが違う環境での話です)。スカラー値であれば、共用体を介してアクセスすることでstrict aliasing ruleをクリアできます。ただ、共用体の配列としてアクセスするのはどうにも不便となってしまいます。このような場合に、「コンパイラ独自の記法によってstrict aliasing ruleを使った最適化を抑える」という手段以外に、未定義の動作を避けつつ正しく行うには、「配列丸ごとコピーする」以外の手段はないものでしょうか。


(注:むろん、このような操作が「処理系定義」となるのは承知ですが、今回はそれについては考えなくて大丈夫です。また、符号なし整数について詰め物ビットはなく、よってトラップ表現が発生する可能性もないものとします)

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

0

質問1:strict aliasing ruleについて調べてみたところ、互換性のない型で「書き込んだ」場合に未定義な動作となるというように読めるのですが、unsigned longで書き込んだものをunsigned long longとして読み取るだけでは未定義の動作とはならない、という解釈で間違いはないでしょうか(もちろん、最後に出る端数についてはunsigned long longアクセスしない前提です)。 

誤っています。strict aliasing rule定義に関する「アクセス」とは、読み取り(read)/変更(modify)の両方を意味します。C99(JIS X 3010:2003)での定義を引用します:

3.1. アクセス(access) <実行時の動作>オブジェクトの値を読み取る,又は変更すること。
参考1. これらの二つの動作のうちのいずれか一方だけを意味する場合は,“読み取る”又は“変更する”という用語を使う。
2. “変更する”は,格納する新しい値が,格納前の値と同じである場合も含む。
3. 評価されない式は,オブジェクトをアクセスしない。

6.5 式
オブジェクトに格納された値に対するアクセスは,次のうちのいずれか一つの型をもつ左辺値によらなければならない。
- (省略)


質問2:(略)このような場合に、「コンパイラ独自の記法によってstrict aliasing ruleを使った最適化を抑える」という手段以外に、未定義の動作を避けつつ正しく行うには、「配列丸ごとコピーする」以外の手段はないものでしょうか。 

必要な要素のみmemcpyすれば良いと思います。

memcpy関数でのアクセスはunsigined char型ポインタとして解釈されるため、strict aliasing ruleによる問題が生じません。C99 TC3(ISO/IEC 9899:TC3)より引用します(JIS X 3010には未反映?):

7.21 String handling <string.h>
For all functions in this subclause, each character shall be interpreted as if it had the type unsigned char (and therefore every possible object representation is valid and has a different value).

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/28 21:26

    ちょっと考えてみたら、片方の型でだけ書き込んでいってもまずいパターンがあることにも気づきました。ありがとうございます。

    キャンセル

0

こんにちは。

実際にやったことはないので、外してたらごめんなさい。

yohhoyさんの日記に解説がありました。
これを見る限り、コンパイラは「非互換な型へのポインタ同士が同じメモリを指すことはない」と仮定して最適化を行ってよいということのようです。
つまり、unsigned long* p0;unsigned long long* p1;は同じメモリを指すことはないという仮定ですね。本来そのようにプログラムしなければいけないのに同じメモリを指していると、最適化の副作用がでるかもしれないが、それはコンパイラのバグではなくプログラマのミスということのようです。

unsigned long* p0 = (unsigned long*)malloc(sizeof(unsigned long)*2);
unsigned long long* p1=(unsigned long long*)p0; // このようなコードを書くべきでない
*p0=1;
*p1=2;
printf("%lu\n", *p0);  // 0や2などではなく、1が出力されても文句は言えない


*p1に書き込むから想定外の結果になりそうですね。書き込まなければどうなのでしょう?

*p0=0;
*(p0+1)=0;
*p0=1;
printf("%llu\n", *p1);  // 0や未初期化な値が出力されても文句は言えないかも?

質問2については、最適化を抑制するためにvolatileを付ければよいように感じます。

unsigned long* volatile p0 = (unsigned long*)malloc(sizeof(unsigned long)*2);
unsigned long long* volatile p1=(unsigned long long*)p0;
*p0=1;
*p1=2;
printf("%lu\n", *p0);  // 0や2が出力される筈(処理系依存)

う~ん、しかし、これが本当なら怖いですね。今までにもやらかしているような気もします。
C++ならC型キャストやreinterprit_cast<>を使ってないなら問題は出ないはず?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/28 19:33

    volatile修飾による回避は、多くの処理系で期待通り振舞うとは思うのですが、言語仕様にのみ厳格に従うと効果がない気がします...

    strict aliasing ruleに厳密に従うのは至難の技だと思います。私も自信がないですねw

    キャンセル

  • 2016/05/28 20:08

    yohhoyさん。

    volatileをつければ、順序が入れ替わったり省略されたりすることなくソース・コード上の記述通りにアクセスされると理解してますが、これは言語仕様ではなく処理系依存な動作なのでしょうか?

    例えば、メモリマップドな2つのI/Oポートから交互に読み出す時、volitaleを付けても指定した通りに読み出すことが言語仕様上保証されないことになります。
    アクセス回数やアクセス順序が重要なLSIはそれなりに存在しますから、それではLSIは意図通りに動作してくれないので、デバイス制御にC/C++を使い辛いということになりそうです。

    キャンセル

  • 2016/05/28 21:01 編集

    「volatileをつければ、順序が入れ替わったり省略されたりすることなくソース・コード上の記述通りにアクセスされる」には同意です。volatile修飾はまさにI/Oポートのような仕組みに対応するためののもですね。

    ここで"厳格に従う"と表現したのは、仕様上はvolatile修飾がaliasing ruleに影響を及ぼすとの言及が一切ないことを根拠としています。言い換えると、言語仕様上はvolatileを使っても上記が期待通り動かない処理系の存在を許すという解釈です。(そのようなものが実在するかには関心がないという態度)

    キャンセル

  • 2016/05/28 21:46

    yohhoyさん。

    なるほど。
    aliasing ruleは変更してもよいルールでvolataileは変更してはいけないことの指定ですので、volataile指定が勝って欲しいところですね。
    volatile指定しているのにaliasing ruleで順序変更や省略されると辛いです。

    キャンセル

0

メモリ領域に対する操作なら実用上は問題ないように思います。というか、同じようなことは私もこれまで何度となくやっていたような気が……。
unsigned longのポインタもunsigned long longのポインタも、同じアドレスを指している限り、同じアドレスへの操作となるはずです。最適化において「ある変数が別のポインタ変数によって書き換えられる可能性を考慮するかどうか」が問題となるのであって、ご質問のように両方ともポインタによる間接アクセスでは該当しないような気がします。そのような手法はテクニックとしてはごく一般的ですし。

と、書いてみたものの、命令スケジューリングで前後して意図した通りにならない可能性は否定できないですね。コンパイラーがどこまで頑張るか(空気読むか)にもよるでしょうけど。
unionを使うのが無難でしょうか。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/28 21:29

    自分が書いていたコードの場合、「mallocで取った領域に書き込みが一度だけ」という状況なので、現実的に問題が出る状況は…ぱっとは思いつけないです。もっとも、「未定義」の不発弾を抱えたままのコードは気持ち悪いので、共用体でエイリアシングを明示して逃げることにしました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.37%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る