C Language
エイリアシングと効果的なタイプ
サーチ…
備考
エイリアシングルールの違反とオブジェクトの有効なタイプに違反することは、2つの異なることであり、混乱させるべきではありません。
エイリアシングは、同じオブジェクトを参照する2つのポインタ
a
とb
のプロパティで、b
a == b
です。データオブジェクトの有効な型は、Cがそのオブジェクトに対して実行可能な操作を決定するために使用されます。特に、有効な型は、2つのポインタが互いにエイリアスできるかどうかを判断するために使用されます。
一つのポインタを介してオブジェクトを変更するので、エイリアシングは、最適化の問題であることができると言う、他のポインタを介して表示されているオブジェクトを変更することができa
b
。あなたのCコンパイラは、その型と出所にかかわらず、ポインタが常に互いをエイリアスできると仮定しなければならない場合、多くの最適化の機会が失われ、多くのプログラムが遅く実行されます。
Cの厳密なエイリアシング規則は、どのオブジェクトが互いにエイリアスを行う(またはしない)かをコンパイラのケースが想定する場合を指します。データポインタには常に留意すべき2つの経験則があります。
別の言い方をしない限り、同じ基本タイプを持つ2つのポインタがエイリアスになることがあります。
異なる基本型を持つ2つのポインタは、2つの型の少なくとも1つが文字型でない限り、エイリアスできません。
ここでは基本型は、我々のようなタイプの資格を脇に置くことを意味const
場合は例えば、あるa
double*
とb
あるconst double*
、コンパイラは変化があることを前提としなければならない一般*a
変更されることがあり*b
。
第2のルールに違反すると、致命的な結果を招く可能性があります。ここで、厳密なエイリアシング規則に違反するということは、実際には同じオブジェクトを指すコンパイラb
、異なる型の2つのポインタa
とb
を提示することを意味します。コンパイラは、常に異なるオブジェクトへの2点をとることができる、とのそのアイデア更新されません*b
あなたはを通してオブジェクトを変更した場合*a
。
そうすると、プログラムの動作が未定義になります。したがって、Cはポインタの変換に非常に厳しい制約を課し、誤ってそのような状況が発生するのを防ぐのに役立ちます。
ソースまたはターゲットタイプが
void
でない限り、異なる基本タイプを持つポインター間のすべてのポインター変換は明示的でなければなりません。
つまり、 const
などの修飾子をターゲットタイプに追加するだけの変換を行わない限り、変換はキャストが必要です。
一般的にポインタの変換やキャストを避けると、エイリアスの問題からあなたを保護します。あなたが本当にそれらを必要としない限り、これらのケースは非常に特殊です。できるだけ避けるべきです。
文字タイプには、非文字タイプではアクセスできません。
オブジェクトが静的、スレッド、または自動記憶期間で定義され、 char
、 unsigned char
、またはsigned char
いずれかの文字タイプを持つオブジェクトの場合は、非文字タイプではアクセスできません。次の例では、 char
配列はint
型として再解釈され、その動作はint
ポインタb
逆参照ごとに未定義です。
int main( void )
{
char a[100];
int* b = ( int* )&a;
*b = 1;
static char c[100];
b = ( int* )&c;
*b = 2;
_Thread_local char d[100];
b = ( int* )&d;
*b = 3;
}
有効な型を持つデータオブジェクトには、文字型ではない別の型からアクセスすることはできません。ここでは他の型はint
型なので、これは許されません。
整列とポインタのサイズが合っていても、このルールから除外されるわけではありませんが、動作は未定義です。
これは特に、標準Cでは、 malloc
または同様の関数が受け取ったバッファを使うように、異なる型のポインタを通して使用できる文字型のバッファオブジェクトを予約する方法がないことを意味します。
上記の例と同じ目標を達成する正しい方法は、 union
を使用することです。
typedef union bufType bufType;
union bufType {
char c[sizeof(int[25])];
int i[25];
};
int main( void )
{
bufType a = { .c = { 0 } }; // reserve a buffer and initialize
int* b = a.i; // no cast necessary
*b = 1;
static bufType a = { .c = { 0 } };
int* b = a.i;
*b = 2;
_Thread_local bufType a = { .c = { 0 } };
int* b = a.i;
*b = 3;
}
ここで、 union
は、コンパイラが最初からバッファが異なるビューを通してアクセスできることを知っていることを保証します。これには、すでにバッファに " int
"型のポインタがあり、ポインタ変換が必要ない "view" ai
があるという利点もあります。
有効なタイプ
データオブジェクトの有効なタイプは、それに関連付けられた最後のタイプの情報(存在する場合)です。
// a normal variable, effective type uint32_t, and this type never changes
uint32_t a = 0.0;
// effective type of *pa is uint32_t, too, simply
// because *pa is the object a
uint32_t* pa = &a;
// the object pointed to by q has no effective type, yet
void* q = malloc(sizeof uint32_t);
// the object pointed to by q still has no effective type,
// because nobody has written to it
uint32_t* qb = q;
// *qb now has effective type uint32_t because a uint32_t value was written
*qb = 37;
// the object pointed to by r has no effective type, yet, although
// it is initialized
void* r = calloc(1, sizeof uint32_t);
// the object pointed to by r still has no effective type,
// because nobody has written to or read from it
uint32_t* rc = r;
// *rc now has effective type uint32_t because a value is read
// from it with that type. The read operation is valid because we used calloc.
// Now the object pointed to by r (which is the same as *rc) has
// gained an effective type, although we didn't change its value.
uint32_t c = *rc;
// the object pointed to by s has no effective type, yet.
void* s = malloc(sizeof uint32_t);
// the object pointed to by s now has effective type uint32_t
// because an uint32_t value is copied into it.
memcpy(s, r, sizeof uint32_t);
後者の場合、そのオブジェクトへのポインタuint32_t*
持っている必要はありませんでした。別のuint32_t
オブジェクトをコピーしたという事実で十分です。
厳密なエイリアシング規則に違反する
次のコードでは、単純にfloat
とuint32_t
サイズが同じであると仮定します。
void fun(uint32_t* u, float* f) {
float a = *f
*u = 22;
float b = *f;
print("%g should equal %g\n", a, b);
}
u
とf
は異なる基底型を持つため、コンパイラは異なるオブジェクトを指していると見なすことができます。 a
とb
2つの初期化の間に*f
が変更されている可能性はないので、コンパイラはコードを
void fun(uint32_t* u, float* f) {
float a = *f
*u = 22;
print("%g should equal %g\n", a, a);
}
つまり、 *f
2回目のロード操作を完全に最適化することができます。
この関数を "通常"と呼ぶと、
float fval = 4;
uint32_t uval = 77;
fun(&uval, &fval);
すべてうまく行き、何かが好き
4は4に等しくなければならない
印刷されます。しかし、同じポインタをチートして渡すと、それを変換した後、
float fval = 4;
uint32_t* up = (uint32_t*)&fval;
fun(up, &fval);
厳密なエイリアシング規則に違反します。その後、動作は未定義になります。コンパイラが2番目のアクセスを完全に異なったものにしていて、プログラムが完全に信頼できない状態になった場合、出力は上記のようになります。
資格を制限する
同じ型の2つのポインタ引数を持つ場合、コンパイラは何も仮定することができず、常に*e
への変更が*f
変更できると仮定する必要があります:
void fun(float* e, float* f) {
float a = *f
*e = 22;
float b = *f;
print("is %g equal to %g?\n", a, b);
}
float fval = 4;
float eval = 77;
fun(&eval, &fval);
すべてうまく行き、何かが好き
4は4に等しいか?
印刷されます。同じポインタを渡すと、プログラムは正しいことを行い、印刷します
4は22に等しいか?
これは、私たちがいることを、いくつかの外部の情報で知っていれば、非効率的であることが判明することができますe
とf
同じデータオブジェクトを指すことはありません。ポインタパラメータにrestrict
修飾子を追加することで、その知識を反映させることができます:
void fan(float*restrict e, float*restrict f) {
float a = *f
*e = 22;
float b = *f;
print("is %g equal to %g?\n", a, b);
}
次に、コンパイラは常にe
とf
が別のオブジェクトを指していると仮定します。
バイトの変更
オブジェクトが効果的なタイプを持っているしたら、他の型が文字型、でない限り、あなたは、別の型のポインタを通してそれを変更しようとするべきではないchar
、 signed char
またはunsigned char
。
#include <inttypes.h>
#include <stdio.h>
int main(void) {
uint32_t a = 57;
// conversion from incompatible types needs a cast !
unsigned char* ap = (unsigned char*)&a;
for (size_t i = 0; i < sizeof a; ++i) {
/* set each byte of a to 42 */
ap[i] = 42;
}
printf("a now has value %" PRIu32 "\n", a);
}
これは有効なプログラムで、
今は価値がある707406378
これは次の理由で機能します。
-
unsigned char
型で見られる個々のバイトに対してアクセスが行われるため、各変更は明確に定義されます。 - オブジェクトへの2つのビュー(
a
およびthrough*ap
、alias)ですが、ap
は文字型へのポインタなので、厳密なエイリアシング規則は適用されません。したがって、コンパイラはa
の値がfor
ループで変更されている可能性があると仮定しなければなりません。a
の変更された値はa
変更されたバイトから構成されなければなりません。 -
a
、uint32_t
のタイプにはパディングビットはありません。表現のそのすべてのビットは、ここでは707406378
の値をカウントし、トラップ表現はありません。