62733


現在、C言語(not C++)の型変換関係でstrict aliasing
rulesについて調べていて、2点疑問が浮かんだので、知恵をお借りしたく思います。

C11規格上では、strict aliasing rulesの規定は「オブジェクトは互換性のある型をもつ _lvalue_
によってのみ、その保存された値にアクセスされなければならない」という書き方になっていると思います。この「アクセス」について、以下は規格違反になるでしょうか？
あるいは、規格違反になるとした場合、以下のどのタイミングで規格違反になるのでしょうか。

[code]

    int i = 0;
    void *vp = &i;         /* 1. ここは問題ないはず */
    long *lp = vp;         /* 2. longへのポインタに代入した時点？ */
    printf("%ld\n", *lp);  /* 3. longとして変数をデリファレンスした時点？ */
    
[/code]

私個人の見解としては、「オブジェクトへのアクセス」が発生するのは3の時点なのではと思っているので、3がstrict aliasing
rulesに引っかかると思っているのですが、この見解は合っていますでしょうか。

また、`malloc()`等で確保したメモリに対して、strict aliasing rulesは適用されるのでしょうか？ 例えば、[stack
overflowのこの質問](https://stackoverflow.com/questions/98650/what-is-the-strict-
aliasing-rule)のトップアンサーにある、

[code]

        // Get a 32-bit buffer from the system
        uint32_t* buff = malloc(sizeof(Msg));
    
        // Alias that buffer through message
        Msg* msg = (Msg*)(buff);
    
[/code]

このコードにおける「オブジェクト」は`malloc()`によって確保されたものですが、これは私の考えでは特定の型に依存するものではないように思います。この場合も、何か特定の型に依存した解釈がなされるものでしょうか。


CC BY-SA 4.0
2020-02-01T05:41:48.900
62690
2020-02-04T05:05:58.453
2020-02-01T05:43:30.690
19110
37670
question
c
strict aliasing rulesにおけるアクセスの基準


第三者読者の役に立つよう alias （別名）とは何かの解説から (TL;DR)

[code]

    int i;
    int* p=&i;
    i=1;
    *p+=1;
    
[/code]

[c](/questions/tagged/c "'c' のタグが付いた質問を表示") ではポインタ
([c++](/questions/tagged/c%2b%2b "'c++' のタグが付いた質問を表示") では参照も)
を経由すると同一の記憶域（あえてメモリと書かない）に２つ以上の方法でアクセスできます。上記 `i` と `*p` がそれです。こういうのを alias
と言います。

先のコードは無駄っぽいけど悪いことしてないように見える。では何が問題か？ [c](/questions/tagged/c "'c'
のタグが付いた質問を表示") はすごく古い言語なので、むかしむかしから維持されているコードではこの alias
を(コンパイラから見て)ひどい使い方していることがあります。

[code]

    int aliastest(int i) { // int が 16bit と仮定する
        char* p=(char*)&i; // 無理やり型変換して
        char c=p[0]; p[0]=p[1]; p[1]=c; // 元変数を型違い alias で変更
        return i; // char* の先に書き込んで int が変わることを期待してる
    }
    
[/code]

元の変数と違う型のポインタ経由のアクセスが１つあるだけでソース上アクセスしていないように見える変数が無節操に変わりうるとすると

[code]

    int global_int;
    void func(double* p) {
        *p=1; // ここで global_int は変更されるのか否か
        otherfunc(global_int);
     }
    
[/code]

もし `global_int`
が変化する前提でしか最適化できないとしたら、コンパイラの実装者がどんなに頑張ってもポインタアクセスがあった時点で最適化のほとんどが適用できなくなります。

そのためアグレッシブな最適化をして良いパターンとダメなパターンをはっきり文章化して、コンパイラの実装者とコンパイラのユーザの両方に周知徹底させようという状況の１つが
strict aliasing rule です。

以下引用元は JIS X 3010:2003 ([c99](/questions/tagged/c99 "'c99' のタグが付いた質問を表示"))
もっと新しい版だと変更があるかも

> 6.5 式 の後ろのほう  
>
> オブジェクトに格納された値に対するアクセスは、次のうちのいずれか１つの型を持つ左辺値によらなければならない。注釈(73)この列挙は、オブジェクトに別名を付けてもよい状況と、付けてはならない状況を規定するためのものである。  
>  \- オブジェクトの有効型と適合する型  
>  \- オブジェクトの有効型と適合する型の修飾版  
>  \- オブジェクトの有効型に対応する符号付き型又は符号無し型  
>  \- オブジェクトの有効型の修飾版に対応する符号付き型又は符号無し型  
>  \- メンバの中に上に列挙した型の１つを含む集成体型又は共用体型（再帰的に包含）  
>  \- 文字型

なので例えば  
\- `int` と `unsigned int` と `volatile signed int` は alias 可能  
\- `struct foo_type { short member; };` と `signed short` は alias 可能  
\- `char` は任意の型と alias 可能

alias 可能とは、既に書いた通りアグレッシブな最適化を禁じるということです。

ではようやく回答

A1. アクセスは 3. の時点です

> 3.1 アクセス  
>  ＜実行時の動作＞オブジェクトの値を読み取る、または変更すること。  
>  参考１．いずれか一方だけを意味する場合は”読み取る”または”変更する”という用語を使う

この例では alias できると規定されていない型なので規格違反（未定義動作）になるでしょう。

A2. 提示の部分コードだけだと「ポインタの型変換」はあっても、それを経由した「オブジェクトへのアクセス」はしていないので、それだけだと判断できないです。

> 6.5 式 の後ろのほう  
>
> 格納された値にアクセスするときのオブジェクトの有効型は（もしあれば）そのオブジェクトの宣言された型とする。注釈(72)割付オブジェクトは宣言された型をもたない。  
>
> 宣言された型を持たないオブジェクトへ、文字型以外の型をもつ左辺値を通じて値を格納した場合、左辺値の型をそのアクセスおよび格納された値を変更しないそれ以後のアクセスでのオブジェクトの有効型とする。

リンク先の SO サンプルコードだと `struct Msg` の `unsigned int`
なメンバにアクセスしていて（よってここでの「オブジェクトの有効型」は `unsigned int` ）その後 alias 可能とされていない
`uint32_t` で別名アクセスしているので違反です、と解説されています。

ちなみにオイラのサンプル `aliastest()` は `char` が alias
可能なのでプログラマが期待した通りの結果が得られるはず。得られないコンパイラは言語仕様逸脱ってことになるっす。


CC BY-SA 4.0
2020-02-03T00:24:43.147
62733
2020-02-04T05:05:58.453
2020-02-04T05:05:58.453
8589
8589
62690
answer