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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

Q&A

解決済

3回答

3721閲覧

strict aliasing ruleについて

maisumakun

総合スコア145184

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

0グッド

0クリップ

投稿2016/05/28 08:35

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を使った最適化を抑える」という手段以外に、未定義の動作を避けつつ正しく行うには、「配列丸ごとコピーする」以外の手段はないものでしょうか。


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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

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

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

投稿2016/05/28 11:29

catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2016/05/28 12:29

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

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 10:16

yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2016/05/28 12:26

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

0

こんにちは。

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

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

C

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

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

C

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

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

C

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

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

投稿2016/05/28 09:27

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yohhoy

2016/05/28 10:33

volatile修飾による回避は、多くの処理系で期待通り振舞うとは思うのですが、言語仕様にのみ厳格に従うと効果がない気がします... strict aliasing ruleに厳密に従うのは至難の技だと思います。私も自信がないですねw
Chironian

2016/05/28 11:08

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

2016/05/28 12:04 編集

「volatileをつければ、順序が入れ替わったり省略されたりすることなくソース・コード上の記述通りにアクセスされる」には同意です。volatile修飾はまさにI/Oポートのような仕組みに対応するためののもですね。 ここで"厳格に従う"と表現したのは、仕様上はvolatile修飾がaliasing ruleに影響を及ぼすとの言及が一切ないことを根拠としています。言い換えると、言語仕様上はvolatileを使っても上記が期待通り動かない処理系の存在を許すという解釈です。(そのようなものが実在するかには関心がないという態度)
Chironian

2016/05/28 12:46

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問