27

char ポインターに適用される厳密なエイリアシング規則を理解しようとしています。

ここでは次のように述べられています。

char* は任意のオブジェクトのエイリアスを参照する可能性があると常に想定されています。

わかりましたので、ソケットコードのコンテキストで、私はこれを行うことができます:

struct SocketMsg
{
   int a;
   int b;
};

int main(int argc, char** argv)
{
   // Some code...
   SocketMsg msgToSend;
   msgToSend.a = 0;
   msgToSend.b = 1;
   send(socket, (char*)(&msgToSend), sizeof(msgToSend);
};

しかし、その後、この声明があります

その逆は正しくありません。char* を char* 以外の任意の型のポインターにキャストし、逆参照することは、通常、厳密なエイリアシング規則に違反しています。

これは、char配列をrecvするときに、メッセージの構造がわかっている場合、構造体へのキャストを再解釈できないことを意味しますか?

struct SocketMsgToRecv
{
    int a;
    int b;
};

int main()
{
    SocketMsgToRecv* pointerToMsg;
    char msgBuff[100];
    ...
    recv(socket, msgBuff, 100);
    // Ommiting make sure we have a complete message from the stream
    // but lets assume msgBuff[0]  has a complete msg, and lets interpret the msg

    // SAFE!?!?!?
    pointerToMsg = &msgBuff[0];

    printf("Got Msg: a: %i, b: %i", pointerToMsg->a, pointerToMsg->b);
}

基本型が char 配列であり、構造体にキャストしているため、この 2 番目の例は機能しませんか? 厳密にエイリアス化された世界で、この状況をどのように処理しますか?

4

2 に答える 2

6

Re @Adam Rosenfield: char* のサプライヤーが同様のことを始めている限り、組合は連携を達成します。

立ち止まって、これが何であるかを理解することは有益かもしれません.

エイリアシング規則の根拠は、コンパイラがアクセスを改善するために異なるメモリ境界に異なる単純な型の値を配置する可能性があるという事実と、場合によってはハードウェアがポインターを使用できるようにするためにそのようなアライメントを必要とする可能性があるという事実です。これは、さまざまなサイズの要素がある構造体にも現れることがあります。構造体は適切な境界で開始することができます。さらに、コンパイラは、構造体の内部にスラックバイトを導入して、それを必要とする構造体要素の適切な位置合わせを実現する場合があります。

多くの場合、コンパイラーには、これらすべてをどのように処理するかどうかを制御するオプションがあることを考えると、驚きが発生する可能性がある多くの方法があることがわかります。これは、構造体 (char* としてキャストされているかどうかに関係なく) へのポインターを、異なるアライメント規則を予期するようにコンパイルされたライブラリーに渡すときに注意することが特に重要です。

char* はどうですか?

char* に関する推定は、sizeof(char) == 1 (他のすべてのサイズの大きいデータのサイズと比較して) であり、char* ポインターにはアラインメント要件がないことです。したがって、本物の char* は常に安全に渡され、アラインメントを気にせずに正常に使用できます。これは、char[] 配列の任意の要素に適用され、ポインターで ++ および -- を実行するなどです。(奇妙なことに、void* はまったく同じではありません。)

これで、ある種の構造体データを、それ自体が適切にアラインされていない char[] 配列に転送した場合、アラインメントが必要なポインターにキャストバックしようとすると、深刻な問題になる可能性があることを確認できるはずです。

char[] 配列と構造体の共用体を作成すると、最も要求の厳しいアラインメント (つまり、構造体のアラインメント) がコンパイラによって受け入れられます。これは、サプライヤーとコンシューマーが一致する共用体を効果的に使用しているため、struct* から char* へのキャストとその逆のキャストが正常に機能する場合に機能します。

その場合、データへのポインターが char* にキャストされる前に、データが同様のユニオンで作成されるか、sizeof(char) バイトの配列として他の方法で転送されることを願っています。また、依存するライブラリと独自のコードの間でコンパイラ オプションに互換性があることを確認することも重要です。

于 2008-11-04T20:19:04.710 に答える
4

正解です。2 番目の例は厳密なエイリアシング ルールに違反しているため、この-fstrict-aliasingフラグを使用してコンパイルすると、正しくないオブジェクト コードが生成される可能性があります。完全に正しい解決策は、ここでユニオンを使用することです。

union
{
  SocketMsgToRecv msg;
  char msgBuff[100];
};

recv(socket, msgBuff, 100);

printf("Got Msg: a: %i, b: %i", msg.a, msg.b);
于 2008-11-04T16:37:48.930 に答える