12

以前の雇用主では、他のコンピューターに「回線を介して」送信する必要のあるバイナリ メッセージを作成していました。各メッセージには、次のような標準ヘッダーがありました。

class Header
{
    int type;
    int payloadLength;
};

すべてのデータは連続していました (ヘッダーの直後にデータが続きます)。ヘッダーへのポインターがあることを前提として、ペイロードに到達したいと考えました。伝統的に、あなたは次のように言うかもしれません:

char* Header::GetPayload()
{
    return ((char*) &payloadLength) + sizeof(payloadLength);
}

あるいは:

char* Header::GetPayload()
{
    return ((char*) this) + sizeof(Header);
}

それはちょっと冗長に思えたので、私は次のように思いつきました:

char* Header::GetPayload()
{
    return (char*) &this[1];
}

最初は戸惑うかもしれませんが、使用するには奇妙すぎるかもしれませんが、非常にコンパクトです。それが素晴らしいか忌まわしいかについて多くの議論がありました。

コーディングに対する犯罪か、それとも素晴らしい解決策か? 同様のトレードオフを経験したことがありますか?

-アップデート:

サイズがゼロの配列を試してみましたが、その時点でコンパイラが警告を出しました。最終的には、継承された手法に行き着きました。メッセージはヘッダーから派生します。実際にはうまく機能しますが、原則として、メッセージ IsA Header を言っているので、少しぎこちないようです。

4

17 に答える 17

42

私はコーディングに対する犯罪に行きます。

どちらの方法でも、まったく同じオブジェクト コードが生成されます。最初のものは、その意図を明確にします。2 つ目は非常に紛らわしく、キーストロークを数回節約できるという唯一の利点があります。(ただタイプすることを学ぶだけです)。

また、どちらの方法も動作が保証されていないことに注意してください。オブジェクトの sizeof() には、単語の配置のためのパディングが含まれているため、ヘッダーが次の場合:

class Header
{
    int type;
    int payloadLength;
    char  status;
};

あなたが説明するどちらの方法でも、実際にはヘッダー + 9 から始まる可能性が最も高いペイロードがヘッダー + 12 から始まります。

于 2008-10-21T19:48:45.597 に答える
14

個人的には、犯罪があるとすれば、ヘッダーにペイロードを要求することだと思います。

しかし、そのようにする限り、'this+1' はどの方法よりも優れています。

正当化: '&this[1]' は、完全に理解するためにクラス定義を掘り下げる必要のない汎用コードであり、誰かがクラスの名前や内容を変更したときに修正する必要もありません。

ところで、最初の例は人道に対する真の犯罪です。クラスの最後にメンバーを追加すると失敗します。メンバーをクラス内で移動すると、失敗します。コンパイラがクラスをパディングすると、失敗します。

また、コンパイラのクラス/構造体のレイアウトがパケットのレイアウトと一致すると仮定する場合は、問題のコンパイラがどのように機能するかを理解する必要があります。例えば。MSVC では、おそらく について知りたいと思うでしょう#pragma pack

PS: "this+1" や "&this[1]" が読みにくい、または理解しにくいと考えている人が多いのは少し怖いです。

于 2008-10-21T19:47:28.237 に答える
14

特定の方法でクラスをレイアウトするためにコンパイラに依存しています。メッセージを構造体として (レイアウトを定義して) 定義し、メッセージをカプセル化し、メッセージへのインターフェースを提供するクラスを作成します。明確なコード = 優れたコード。「かわいい」コード = 悪い (保守が難しい) コード。

struct Header
{
    int type;
    int payloadlength;
}
struct MessageBuffer
{
   struct Header header;
   char[MAXSIZE] payload;
}

class Message
{
  private:
   MessageBuffer m;

  public:
   Message( MessageBuffer buf ) { m = buf; }

   struct Header GetHeader( )
   {
      return m.header;
   }

   char* GetPayLoad( )
   {
      return &m.payload;
   }
}

私が C++ を書いてからしばらく経っているので、構文の問題は許してください。一般的な考えを伝えようとしているだけです。

于 2008-10-21T20:00:23.220 に答える
13

これは一般的な問題ですが、実際に必要なのはこれです。

class Header
{
    int type;
    int payloadLength;
    char payload[0];

};

char* Header::GetPayload()
{
    return payload;
}
于 2008-10-21T22:24:18.577 に答える
5

私の投票はコーディングホラーです。誤解しないでほしいのですが、これは賢い方法ですが、コードの理解と読み取りがはるかに難しくなるという代償を払って、加算操作全体を 1 つ節約できます。トレードオフに価値があるとは思いません。

于 2008-10-21T19:48:42.587 に答える
5

ヘッダーに含まれていないデータを「返す」必要がある場合、これは最初から欠陥があると思います。

あなたはすでにこれらのハック的な根拠に身を置いているので、私はあなたが思いついたものを本当に気に入っています.

ただし、これは美人コンテストではないことに注意してください。まったく異なる解決策を見つける必要があります。あなたが提示した GetPayload() の 3 つのバージョンすべてについて、あなたの詳細な説明がなければ、何が起こっているのか理解できません。

于 2008-10-21T19:50:52.567 に答える
4

「空の配列メンバー」のトリックを考えましたか? 私はそれを頻繁に見たことを覚えており、1 回か 2 回使用したことさえありますが、本当に良い参考文献を見つけることができないようです (以下で参照されているものを除いて)。

トリックは、構造体を次のように宣言することです

struct bla {
    int i;
    int j;
    char data[0];
}

次に、「データ」メンバーは、ヘッダーの背後にあるものを単に指します。これがどれほど移植性があるかはわかりません。配列サイズとして「1」も見ました。

(以下の URL を参照として使用し、'[1]' 構文を使用すると、長すぎて機能しないように見えました。ここにリンクがあります:)

http://developer.apple.com/documentation/DeveloperTools/gcc-4.0.1/gcc/Zero-Length.html

于 2008-10-21T20:51:23.770 に答える
3

一貫して動作する場合、それはエレガントなソリューションです。

コンパイラがアラインメントの問題に対処し、正しくアラインされたメモリ空間でペイロードがヘッダーに続くと想定できるため、通常はメモリ内で動作します。

使用するストリーミング メカニズムは、おそらく特定の境界でのオブジェクトの整列を気にしないため、Header/Payload オブジェクトが「ネットワーク経由で」ストリーミングされると、これがばらばらになることがわかります。したがって、ペイロードは、特定のアラインメントに強制するためのパディングなしでヘッダーに直接続く場合があります。

一言で言えば、エレガントはエレガントと同じです。したがって、ストリーミング方法に注意する限り、エレガントです。

于 2008-10-21T19:56:01.607 に答える
2

まず、「コーディングに対する犯罪」と「優れた解決策」の間には非常に多くのスペースがありますが、これは前者に近いと言えます。

ヘッダーはペイロードのキーパーですか?

これが根本的な問題です。ヘッダーとペイロードの両方を、メッセージ全体を保持する別のオブジェクトで管理する必要があり、それがペイロードを要求する適切な場所です。そして、ポインター演算や索引付けなしでそれを行うことができます。

それを考えると、何が起こっているのかがより明確になるため、2 番目の解決策をお勧めします。

しかし、そもそも私たちがこのような状況にあるということは、あなたのチームの文化が明快さよりも賢さを重視していることを示しているようです。

本当に可愛くなりたいなら、一般化できます。

template<typename T. typename RetType>
RetType JustPast(const T* pHeader)
{
   return reinterpret_cast<RetType>(pHeader + sizeof(T));
}
于 2008-10-21T20:29:22.350 に答える
1

この時代、C++ では、C スタイルの char* へのキャストは、あまり耳を傾けることなく「素晴らしいデザイン アイデア」賞の資格を失うと思います。

私は行くかもしれません:

#include <stdint.h>
#include <arpa/inet.h>

class Header {
private:
    uint32_t type;
    uint32_t payloadlength;
public:
    uint32_t getType() { return ntohl(type); }
    uint32_t getPayloadLength() { return ntohl(payloadlength); }
};

class Message {
private:
    Header head;
    char payload[1]; /* or maybe std::vector<char>: see below */
public:
    uint32_t getType() { return head.getType(); }
    uint32_t getPayloadLength() { return head.getPayloadLength(); }
    const char *getPayload() { return payload; }
};

もちろん、これは C99 風の POSIX を前提としています。非 POSIX プラットフォームに移植するには、プラットフォームが提供するものに関して、uint32_t と ntohl の一方または両方を自分で定義する必要があります。通常は難しくありません。

理論的には、両方のクラスでレイアウト プラグマが必要になる場合があります。実際には、この場合の実際のフィールドを考えると驚かれることでしょう。この問題は、メモリ内のメッセージのバイトを構築してから一度に書き込むのではなく、一度に 1 フィールドずつ iostream との間でデータを読み書きすることによって回避できます。また、char[] よりも便利なものでペイロードを表すことができることも意味します。つまり、最大メッセージ サイズを設定したり、malloc や配置を変更したりする必要がなくなります。もちろん、多少のオーバーヘッドが発生します。

于 2008-10-22T00:37:54.610 に答える
1

sizeof()VC++ がクラスの値にパディングを課す可能性があることを忘れないでください。提供された例は 8 バイトであると予想されるため、自動的に DWORD で整列されるため、問題ないはずです。確認してください#pragma pack

ただし、提供されている例はある程度のコーディング ホラーであることに同意します。多くの Win32 データ構造には、可変長データが続く場合、ヘッダー構造にポインター プレースホルダーが含まれます。これは、メモリに読み込まれたデータを参照する最も簡単な方法です。MAPISRowSet構造は、このアプローチの一例です。

于 2008-10-21T20:03:44.943 に答える
1

私も実際に似たようなことをしていますし、これまでに書かれたほぼすべての MMO やオンライン ビデオ ゲームもそうです。それらには「パケット」という概念があり、各パケットには独自のレイアウトがあります。だからあなたは持っているかもしれません:

struct header
{
    short id;
    short size;
}

struct foo
{
    header hd;
    short hit_points;
}


short get_foo_data(char *packet)
{
    return reinterpret_cast<foo*>(packet)->hit_points;
}

void handle_packet(char *packet)
{
    header *hd = reinterpret_cast<header*>(packet);
    switch(hd->id)
    {
        case FOO_PACKET_ID:
            short val = get_foo_data(packet);
        //snip
    }
}

そして、彼らはパケットの大部分に対してそれを行います. 一部のパケットには明らかに動的なサイズがあり、それらのメンバーに対しては、長さのプレフィックス付きフィールドといくつかのロジックを使用してそのデータを解析します。

于 2008-10-21T21:37:15.007 に答える
1

私に関する限り、それらは基本的に同じものです。どちらもバイト ジャグリングの一種であり、常にリスクが伴いますが、正しく行うのは不可能ではありません。最初の形式は、もう少し受け入れられ、認識しやすいものです。私は個人的に書くだろう:

char* Header::GetPayload()
{
    return ((char*) this) + sizeof(*this);
}
于 2008-10-21T19:50:00.527 に答える
0

上記に加えて、これは相互運用性と優れたワイヤプロトコル設計原則に対する犯罪であると言えます。ワイヤープロトコルの定義とその実装を明確に区別できない/喜んでいるプログラマーの数は本当に驚くべきことです。プロトコルが2日以上存続する必要がある場合は、おそらく2年以上存続する必要があります/ OS /コンパイラ/言語/エンディアンネス、ある時点で、プロトコルは、遅かれ早かれ壊れます。したがって、他の人々の生活を楽にし、ワイヤプロトコル仕様を書き留め、さらに適切な(逆)シリアル化ルーチンを記述します。そうでなければ、人々はそれほど楽しい文脈であなたの名前に言及し続けるでしょう。

于 2009-02-03T07:45:41.420 に答える
0

「犯罪」のような言葉は使いたくない。むしろ、&this [1]は、コンパイラが同意しない可能性のあるメモリレイアウトについての仮定をしているように見えることを指摘したいと思います。たとえば、コンパイラは、それ自体の理由(アライメントなど)により、構造体の任意の場所にダミーバイトを挿入する場合があります。コンパイラーやオプションが変更された場合に正しいオフセットを取得することがより保証されている手法をお勧めします。

于 2009-01-06T00:11:25.127 に答える
0

&this[1]に投票します。メモリにロードされたファイル(受信したパケットも同様に含めることができます)を解析するときに、かなりの量が使用されるのを見てきました。初めて見たときは少し奇妙に見えるかもしれませんが、それが何を意味するのかはすぐにわかるはずです。それは明らかに、このオブジェクトを過ぎたメモリのアドレスです。間違えにくいのでいいですね。

于 2008-10-21T23:39:40.890 に答える
0

おそらく、詳細な方法を使用する必要がありましたが、#define マクロに置き換えましたか? このようにして、入力時に省略形を使用できますが、コードをデバッグする必要がある人は誰でも問題なく進めることができます。

于 2008-10-21T19:48:47.563 に答える