このようなコードには多くの利点がありますが、残念なことにC標準はそれを容易にするために作成されていません。コンパイラは歴史的に、標準が必要とする以上の効果的な動作保証を提供してきたため、標準Cで可能なコードよりもはるかにきれいにコードを記述できるようになりましたが、コンパイラは最近、最適化という名前でそのような保証の取り消しを開始しました。
最も注目すべきことは、多くのCコンパイラは、2つの構造体型に同じ初期シーケンスが含まれている場合、型が無関係であっても、いずれかの型へのポインターを使用してその共通シーケンスのメンバーにアクセスできることを歴史的に保証しています(ドキュメントではない場合)さらに、共通の初期シーケンスを確立するために、構造体へのすべてのポインターが同等であること。そのような動作を利用するコードは、そうでないコードよりもはるかにクリーンでタイプセーフになりますが、残念ながら、標準では共通の初期シーケンスを共有する構造を同じ方法でレイアウトする必要があるにもかかわらず、実際に使用することは禁止されています別の初期シーケンスにアクセスするための1つのタイプのポインター。
そのため、Cでオブジェクト指向コードを記述したい場合は、Cのポインター型のルールを順守するために多くのフープを飛び越えて、古いコンパイラが意図したとおりに動作するコードを生成したとしても、スリップした場合、現代のコンパイラは無意味なコードを生成します。または、コードが古いスタイルのポインタ動作をサポートするように構成されたコンパイラでのみ使用できるという要件を文書化します「-fno-strict-aliasing」)一部の人々は「-fno-strict-aliasing」を悪と見なしますが、「-fno-strict-aliasing」Cを言語であると考える方がより役立つことをお勧めします。 「標準」Cよりも大きな目的のセマンティックパワーを提供します。ただし、他の目的には重要となる可能性のある最適化を犠牲にします。
例として、従来のコンパイラでは、歴史的なコンパイラは次のコードを解釈します。
struct pair { int i1,i2; };
struct trio { int i1,i2,i3; };
void hey(struct pair *p, struct trio *t)
{
p->i1++;
t->i1^=1;
p->i1--;
t->i1^=1;
}
次の手順を順番に実行します。の最初のメンバーをインクリメントし、の最初のメンバーの*p
最下位ビットを補完し*t
、次にの最初のメンバーをデクリメントし、の最初のメンバーの*p
最下位ビットを補完し*t
ます。最新のコンパイラは、異なるオブジェクトp
をt
識別し、識別できる場合にコードが効率的になるように操作の順序を並べ替えますが、そうでない場合は動作を変更します。
もちろん、この例は意図的に工夫されており、実際には、あるタイプのポインターを使用して別のタイプの共通の初期シーケンスの一部であるメンバーにアクセスするコードが通常は機能しますが、残念ながらそのようなコードがいつ失敗するかを知る方法はありません型ベースのエイリアス解析を無効にすることを除いて、安全に使用することはまったくできません。
任意の型への2つのポインターの交換のようなことを行う関数を作成したい場合、やや不自然な例が発生します。「1990年代のC」コンパイラの大部分では、次の方法で実現できます。
void swap_pointers(void **p1, void **p2)
{
void *temp = *p1;
*p1 = *p2;
*p2 = temp;
}
ただし、標準Cでは、以下を使用する必要があります。
#include "string.h"
#include "stdlib.h"
void swap_pointers2(void **p1, void **p2)
{
void **temp = malloc(sizeof (void*));
memcpy(temp, p1, sizeof (void*));
memcpy(p1, p2, sizeof (void*));
memcpy(p2, temp, sizeof (void*));
free(temp);
}
場合*p2
割り当てられたストレージに保持され、そして一時的なポインタが割り当てられたストレージに保持されていない、の有効なタイプは、*p2
試みが使用する一時的なポインタの種類、およびコードとなるであろう*p2
一時的なポインタと一致しなかった任意のタイプとしてタイプは未定義の動作を呼び出します。コンパイラーがそのようなことに気付く可能性は非常に低いはずですが、現代のコンパイラー哲学では、プログラマーがすべてのコストで未定義の動作を回避する必要があるため、割り当てられたストレージを使用せずに上記のコードを記述する他の安全な方法は考えられません。