Oracle Solaris Studio 12.2: C ユーザーガイド

第 5 章 型に基づく別名解析

この章では、xalias_level オプションといくつかのプラグマを使用して、型に基づく別名解析および最適化を実行する方法について説明します。これらの拡張機能を使用すると、ユーザーの C 言語プログラムでのポインタの使用方法について、型に基づく情報を示すことができます。C コンパイラはそれらの情報を利用し、ユーザーのプログラムにおけるポインタベースのメモリー参照の別名明確化について、非常に効率性の高いジョブを実行します。

このコマンドの構文の詳細については、「B.2.72 -xalias_level[= l]」を参照してください。また、lint プログラムの型に基づく別名解析機能については、「4.3.38 -Xalias_level[=l ] を参照してください。

5.1 型に基づく解析の概要

-xalias_level オプションを使用すると、7 つの別名レベルのいずれか 1 つを指定できます。各レベルは、ユーザーの C 言語プログラムでのポインタの使用方法について、特定のプロパティーセットを指定します。

コンパイル時に -xalias_level オプションを上位に設定していくと、コンパイラは、コードのポインタに関する仮定を徐々に拡張していきます。コンパイラの作成する仮定が少ないと、それだけプログラミングの自由度が向上します。ただし、狭い仮定で実行された最適化により、実行時のパフォーマンスが向上しないことがあります。より上位レベルの -xalias_level オプションから得られる仮定に従ってコードを作成すると、最終的な最適化で実行時のパフォーマンスが向上する可能性が高くなります。

-xalias_level オプションは、各翻訳単位に適用される別名レベルを指定します。より詳細に設定したほうがよい場合、新しいプラグマを使用すると、適用されている別名レベルを無効にし、個々の型または翻訳単位のポインタ変数間の別名設定の関係性を明示的に指定できます。これらのプラグマは、翻訳単位におけるポインタの用法がいずれかの別名レベルで扱われていても、少数の特定ポインタ変数がいずれかのレベルで許可されていない不規則な方法で使用される場合にもっとも役立ちます。

5.2 微調整におけるプラグマの使用

より詳細に設定したほうが型に基づいた解析に望ましい場合、次のプラグマを使用すると、適用されている別名レベルを無効にし、個々の型または翻訳単位のポインタ変数間で別名設定の関係性を指定できます。これらのプラグマは、翻訳単位におけるポインタの用法がいずれかの別名レベルと一貫していても、少数の特定ポインタ変数がいずれかのレベルで許可されていない不規則な方法で使用される場合にもっとも役立ちます。


注 –

プラグマより先に命名済みの型または変数を宣言する必要があります。この作業を怠ると、警告メッセージが発行され、プラグマが無視されます。プラグマの意味の適用される最初のメモリー参照のあとにプラグマを配置した場合、プログラムは未定義の結果を生成します。


プラグマの定義において、次の用語を使用します。

用語  

意味  

level

「B.2.72 -xalias_level[= l]」に一覧表示されている任意の別名レベル

type

次のいずれかです。 

  • charshortintlonglong longfloatdoublelong double

  • void。すべてのポインタの型を示します。

  • typedef nametypedef 宣言で定義される型の名前。

  • struct namestruct tag 名が後続するキーワード struct のことです。

  • unionunion tag 名が後続するキーワード union のことです。

pointer_name

翻訳単位におけるポインタ型の変数の名前 

5.2.1 #pragma alias_level level (list)

level は、次の別名レベルのいずれかと置き換えます。anybasicweaklayoutstrictstd、または stronglist は、単一の型またはコンマで区切った型のリストと置き換えることも、単一のポインタまたはコンマで区切ったポインタのリストで置き換えることもできます。たとえば、#pragma alias_level を次のように発行できます。

このプラグマは、指定の別名レベルがリストの型に対応する翻訳単位のすべてのメモリー参照、または命名済みのポインタ変数が参照解除されている翻訳単位のすべての参照解除に適用されることを指定します。

特定の参照解除に対し複数の別名レベルを指定した場合、ポインタ名によって適用されたレベルがほかのすべてのレベルに優先します。型名によって適用されたレベルは、オプションによって適用されたレベルに優先します。次の例では、#pragma alias_levelany より上位に設定してプログラムをコンパイルした場合に、std レベルが p に適用されます。


typedef int * int_ptr;
int_ptr p;
#pragma alias_level strong (int_ptr)
#pragma alias_level std (p)

5.2.1.1 #pragma alias (type, type [, type]…)

このプラグマは、リストされているすべての型が相互に別名設定することを指定します。次の例では、コンパイラは、間接アクセス *pt が間接アクセス *pf を別名設定することを仮定します。


#pragma alias (int, float)
int *pt;
float *pf;

5.2.1.2 #pragma alias (pointer, pointer [, pointer] …)

このプラグマは、命名済みのポインタ変数の参照解除の地点で、参照解除されているポインタ値がそのほかの命名済みポインタ変数と同じオブジェクトをポイントできることを指定します。ただし、ポインタは、命名済みの変数に含まれるオブジェクトだけに制限されず、リストに含まれていないオブジェクトをポイントできます。このプラグマは、適用される別名レベルの別名設定仮定を無効にします。次の例では、プラグマに続く間接アクセス pq が (2 つのポインタの型に関係なく) 別名設定すると見なされます。


#pragma alias(p, q)

5.2.1.3 #pragma may_point_to (pointer, variable [, variable] …)

このプラグマは、命名済みのポインタ変数の参照解除の地点で、参照解除されているポインタ値が命名済みの変数に含まれているオブジェクトにポイントできることを指定します。ただし、ポインタは、命名済みの変数に含まれるオブジェクトだけに制限されず、リストに含まれていないオブジェクトをポイントできます。このプラグマは、適用される別名レベルの別名設定仮定を無効にします。次の例では、コンパイラは、間接アクセス *p が直接アクセス ab、および c を別名設定すると仮定します。


#pragma alias may_point_to(p, a, b, c)

5.2.1.4 #pragma noalias (type, type [, type]…)

このプラグマは、リストされている型が相互に別名設定しないことを指定します。次の例では、コンパイラは、間接アクセス *p が間接アクセス *ps を別名設定しないと仮定します。


struct S {
   float f;
   ...} *ps;

#pragma noalias(int, struct S)
int *p;

5.2.1.5 #pragma noalias (pointer, pointer [, pointer] …)

このプラグマは、命名済みのポインタ変数の参照解除の地点で、参照解除されているポインタがそのほかの命名済みポインタ変数と同じオブジェクトをポイントしないことを指定します。このプラグマは、適用されているそのほかすべての別名レベルを無効にします。次の例では、コンパイラは、間接アクセス *p が間接アクセス *q を (2 つのポインタの型に関係なく) 別名設定しないことを仮定します。


#pragma noalias(p, q)

5.2.1.6 #pragma may_not_point_to (pointer, variable [, variable] …)

このプラグマは、命名済みのポインタ変数の参照解除の地点で、参照解除されているポインタ値が命名済みの変数に含まれているオブジェクトをポイントしないことを指定します。このプラグマは、適用されているそのほかすべての別名レベルを無効にします。次の例では、コンパイラは、間接アクセス *p が直接アクセス ab、または c を別名設定しないことを仮定します。


#pragma may_not_point_to(p, a, b, c)

5.3 lint によるチェック

lint プログラムは、同一レベルの型に基づく別名の明確化を、コンパイラの -xalias_level コマンドとして認識します。また、lint プログラムは、この章で説明されている型に基づく別名の明確化に関連するプラグマも認識します。-Xalias_level コマンドの詳細は、「4.3.38 -Xalias_level[=l ]を参照してください。

lint が検出を行い、警告メッセージを生成する状況は 4 つあります。

5.3.1 構造体ポインタへのスカラーポインタのキャスト

次の例では、integer 型のポインタ p が struct foo 型のポインタとしてキャストされます。lint -Xalias_level=weak (またはそれ以上) を指定すると、エラーが生成されます。


struct foo {
    int a;
    int b;
  };

struct foo *f;
int *p;

void main()
{
    f = (struct foo *)p; /* struct pointer cast of scalar pointer error */
}

5.3.2 構造体ポインタへの void ポインタのキャスト

次の例では、void 型のポインタ vp が構造体のポインタとしてキャストされます。lint -Xalias_level=weak (またはそれ以上) を指定すると、警告メッセージが生成されます。


struct foo {
    int a;
    int b;
  };

struct foo *f;
void *vp;

void main()
{
    f = (struct foo *)vp; /* struct pointer cast of void pointer warning */
}

5.3.3 構造体ポインタへの構造体フィールドのキャスト

次の例では、構造体メンバーのアドレス foo.b は構造体ポインタとしてキャストされ、f2 に割り当てられます。lint -Xalias_level=weak (またはそれ以上) を指定すると、エラーが生成されます。


struct foo{
    int a;
    int b;
  };

struct foo *f1;
struct foo *f2;

void main()
{
    f2 = (struct foo *)&f1->b; /* cast of a scalar pointer to struct pointer error*/
}

5.3.4 明示的な別名設定が必要

次の例では、struct fooa 型のポインタ f1 が型 struct foob 型のポインタとしてキャストされています。lint で -Xalias_level=strict (またはそれ以上) を指定する場合、構造体の型がまったく同じ (同じ型で同数の構造体フィールド) でないかぎり、このようなキャストは明示的な別名設定を必要とします。また、別名レベルが standardstrong の場合、別名設定を実行するにはタグの一致が必要であると仮定されます。f1 への割り当て前に #pragma alias (struct fooastruct foob) を実行すると、lint は警告メッセージの生成を停止します。


struct fooa {
    int a;
};

struct foob {
    int b;
};

struct fooa *f1;
struct foob *f2;

void main()
{
    f1 = (struct fooa *)f2; /* explicit aliasing required warning */
}

5.4 メモリー参照の制限の例

ここでは、実際のソースファイルに登場する可能性の高いコードの例を説明します。それぞれの例のあとに、コンパイラの仮定について説明します。これらの仮定は、適用レベルの型に基づいた解析によって作成されます。

5.4.1 例 1

次のコードを考えてみましょう。さまざまなレベルの別名設定でコンパイルすることにより、それぞれの型の別名設定の関係性を理解できます。

struct foo {
    int f1;
    short f2;
    short f3;
    int f4;
} *fp;

struct bar {
    int b1;
    int b2;
    int b3;
} *bp;

int *ip;
short *sp;

この例が -xalias_level=any オプションでコンパイルされる場合、コンパイラは次の間接アクセスを相互の別名とみなします。

*ip*sp*fp*bpfp->f1fp->f2fp->f3fp->f4bp->b1bp->b2bp->b3

この例が -xalias_level=basic オプションでコンパイルされる場合、コンパイラは次の間接アクセスを相互の別名とみなします。

*ip*bpfp->f1 fp->f4bp->b1bp->b2 bp->b3

また、*spfp->f2、および fp->f3 は相互に別名設定でき、*sp および *fp も相互に別名設定できます。

しかし、-xalias_level=basic を指定した場合、コンパイラは次のように仮定します。

2 つの間接アクセスの基本型が異なるため、コンパイラはこれらの仮定を作成します。

この例が -xalias_level=weak オプションでコンパイルされる場合、コンパイラは、次の別名情報を仮定します。

コンパイラは、fp->fp1bp->b2 を別名設定しないと仮定します。これは、f1 が構造体に 0 のオフセットを保持するフィールドである一方で、b2 が構造体に 4 バイトのオフセットを保持するフィールドであるからです。同様に、コンパイラは、fp->f1bp->b3 を別名設定せず、fp->f4bp->b1 または bp->b2 を別名設定しないと仮定します。

この例が -xalias_level=layout オプションでコンパイルされる場合、コンパイラは、次の情報を仮定します。

fp->f4 は、bp->b3 を別名設定しません。これは、f4b3foo および bar の共通初期シーケンスで対応するフィールドではないからです。

この例が -xalias_level=strict オプションでコンパイルされる場合、コンパイラは、次の別名情報を仮定します。

-xalias_level=strict を指定すると、コンパイラは、*fp*bpfp->f1fp->f2fp->f3fp->f4bp->b1bp->b2、および bp->b3 が相互に別名設定しないと仮定します。これは、フィールド名が無視されるときに、foo および bar が同じではないからです。ただし、fpfp->f1 を別名設定し、bpbp->b1 を別名設定します。

この例が -xalias_level=std オプションでコンパイルされる場合、コンパイラは、次の別名情報を仮定します。

ただし、fp->f1 は、bp->b1bp->b2、または bp->b3 を別名設定しません。これは、フィールド名が注視されるときに、foo および bar が同じではないからです。

この例が -xalias_level=strong オプションでコンパイルされる場合、コンパイラは、次の別名情報を仮定します。

5.4.2 例 2

次の例のソースコードを考えてみましょう。さまざまなレベルの別名設定でコンパイルすることにより、それぞれの型の別名設定の関係性を理解できます。

struct foo {
    int f1;
    int f2;
    int f3;
} *fp;

struct bar {
    int b1;
    int b2;
    int b3;
} *bp;

この例が -xalias_level=any オプションでコンパイルされる場合、コンパイラでは、次の別名情報を仮定します。

*fp*bpfp->f1fp->f2fp->f3bp->b1bp->b2、および bp->b3 はすべて相互に別名設定できます。2 つのメモリーアクセスが -xalias_level=any レベルで相互に別名設定するからです。

この例が -xalias_level=basic オプションでコンパイルされる場合、コンパイラでは、次の別名情報を仮定します。

*fp*bpfp->f1fp->f2fp->f3bp->b1bp->b2、および bp->b3 はすべて相互に別名設定できます。すべての構造体フィールドが同じ基本型であるため、ポインタ *fp および *bp を使用する 2 つのフィールドアクセスは、この例において相互に別名設定できます。

この例が -xalias_level=weak オプションでコンパイルされる場合、コンパイラは、次の別名情報を仮定します。

ただし、-xalias_level=weak を指定すると、次の制限が課されます。

この例が -xalias_level=layout オプションでコンパイルされる場合、コンパイラでは、次の別名情報を仮定します。

ただし、-xalias_level=layout を指定すると、次の制限が課されます。

この例が -xalias_level=strict オプションでコンパイルされる場合、コンパイラは、次の別名情報を仮定します。

ただし、-xalias_level=strict を指定すると、次の制限が課されます。

この例が -xalias_level=std オプションでコンパイルされる場合、コンパイラは、次の別名情報を仮定します。

fp->f1fp->f2fp->f3 bp->b1bp->b2、および bp->b3 は相互に別名設定しません。

この例が -xalias_level=strong オプションでコンパイルされる場合、コンパイラは、次の別名情報を仮定します。

fp->f1fp->f2fp->f3 bp->b1bp->b2、および bp->b3 は相互に別名設定しません。

5.4.3 例 3

次の例のソースコードは、特定レベルの別名設定が内部ポインタを処理できないことを示します。内部ポインタの定義については、表 B–13 を参照してください。

struct foo {
        int f1;
        struct bar *f2;
        struct bar *f3;
        int f4;
        int f5;
        struct bar fb[10];
} *fp;

struct bar
        struct bar *b2;
        struct bar *b3;
        int b4;
} *bp;

bp=(struct bar*)(&fp->f2);

この例の参照解除は、weaklayoutstrict、または std でサポートされません。ポインタ割り当て bp=(struct bar*)(&fp->f2) の実行後、対になった 次のメモリーアクセスは同じメモリー位置に接触します。

ただし、オプション weaklayoutstrict、および std を指定する場合、コンパイラは、fp->f2 および bp->b2 が別名設定しないことを仮定します。コンパイラがこのように仮定する理由は、b2 のオフセットが 0 である一方で f2 が 4 バイトのオフセットを保持することと、foo および bar が共通の初期シーケンスを保持しないことにあります。同様に、コンパイラは、bp->b3fp->f3 を別名設定しないこと、および bp->b4fp->f4 を別名設定しないことも仮定します。

そのため、ポインタ割り当てbp=(struct bar*)(&fp->f2) により、別名情報に関するコンパイラの仮定が正しくない状況が作成されます。これにより、最適化が正しく行われない可能性があります。

次の例に示されている変更を行なったあとで、コンパイルを実行してください。


struct foo {
        int f1;
        struct bar fb;   /* Modified line */
#define f2 fb.b2         /* Modified line */
#define f3 fb.b3         /* Modified line */
#define f4 fb.b4         /* Modified line */
        int f5;
        struct bar fb[10];
} *fp;

struct bar
        struct bar *b2;
        struct bar *b3;
        int b4;
} *bp;

bp=(struct bar*)(&fp->f2);

ポインタ割り当て bp=(struct bar*)(&fp->f2) の実行後、対になった 次のメモリーアクセスは同じメモリー位置に接触します。

前述のコード例に示された変更内容を調べることにより、式 fp->f2 が式 fp->fb.b2 のもう 1 つの書式であることが理解できます。fp->fb の型が bar であるため、fp->f2barb2 フィールドにアクセスします。さらに、bp->b2barb2 フィールドにアクセスします。そのため、コンパイラは、fp->f2bp->b2 を別名設定することを仮定します。同様に、コンパイラは、fp->f3bp->b3 を別名設定し、fp->f4bp->b4 を別名設定することも仮定します。その結果、コンパイラの仮定する別名設定は、ポインタ割り当てで設定された実際の別名と一致します。

5.4.4 例 4

次の例のソースコードを考えてみましょう。

struct foo {
        int f1;
        int f2;
} *fp;

struct bar {
        int b1;
        int b2;
} *bp;

struct cat {
        int c1;
        struct foo cf;
        int c2;
        int c3;
} *cp;

struct dog {
        int d1;
        int d2;
        struct bar db;
        int d3;
} *dp;

この例が -xalias_level=weak オプションでコンパイルされる場合、コンパイラは、次の別名情報を仮定します。

fp->f2 は、cp->c2 を別名設定できます。*dp*cp を別名設定でき、*fpdp->db を別名設定できるからです。

cp->cf.f1 は、dp->db.b1 を別名設定しません。

cp->c2dp->db.b1 を別名設定せず、 cp->c2dp->d3 を別名設定しません。

オフセットに関連して、*dpcp->cf を別名設定する場合にかぎり、cp->c2db->db.b1 を別名設定できます。ただし、*dpcp->cf を別名設定する場合、dp->db.b1foo cf の末尾を超えて別名設定する必要がありますが、これはオブジェクトの制限事項で禁じられています。そのため、コンパイラは、cp->c2db->db.b1 を別名設定できないと仮定します。

cp->c3 は、dp->d3 を別名設定できます。

cp->c3 は、dp->db.b2 を別名設定しません。参照解除に関連する型のフィールドのオフセットが異なり、重複することがないため、これらのメモリー参照は別名設定を行いません。この事実に基づき、コンパイラは、それらのメモリー参照が別名設定できないと仮定します。

dp->d3 は、cp->cf.f2 を別名設定しません。参照解除に関連する型のフィールドのオフセットが異なり、重複することがないため、これらのメモリー参照は別名設定を行いません。この事実に基づき、コンパイラは、それらのメモリー参照が別名設定できないと仮定します。

この例が -xalias_level=layout オプションでコンパイルされる場合、コンパイラでは、次の別名情報だけを想定します。

この例が -xalias_level=strict オプションでコンパイルされる場合、コンパイラでは、次の別名情報だけを想定します。

この例が -xalias_level=std オプションでコンパイルされる場合、コンパイラでは、次の別名情報だけを想定します。

5.4.5 例 5

次の例のソースコードを考えてみましょう。

struct foo {
        short f1;
        short f2;
        int   f3;
} *fp;

struct bar {
        int b1;
        int b2;
} *bp;

union moo {
        struct foo u_f;
        struct bar u_b;
} u;

それぞれの別名レベルに基づいて、コンパイラは次のように仮定します。

5.4.6 例 6

次の例のソースコードを考えてみましょう。

struct bar;

struct foo {
        struct foo *ffp;
        struct bar *fbp;
} *fp;

struct bar {
        struct bar *bbp;
        long        b2;
} *bp;

それぞれの別名レベルに基づいて、コンパイラは次のように仮定します。

5.4.7 例 7

次の例のソースコードを考えてみましょう。

struct foo;
struct bar;
#pragma alias (struct foo, struct bar)

struct foo {
        int f1;
        int f2;
} *fp;

struct bar {
        short b1;
        short b2;
        int   b3;
} *bp;

この例のプラグマにより、foo および bar が相互に別名設定できることがコンパイラに伝えられます。コンパイラは、別名情報について次のように仮定します。