web-dev-qa-db-ja.com

C ++には、符号付きのようにエンディアンネス修飾子がないのはなぜですか?

(この質問は多くの型付き言語に当てはまると思いますが、例としてC++を使用することにしました。)

なぜ書くだけの方法がないのですか:

struct foo {
    little int x;   // little-endian
    big long int y; // big-endian
    short z;        // native endianness
};

特定のメンバー、変数、およびパラメーターのエンディアンを指定するには?

署名との比較

変数の型は、値を格納するために使用されるバイト数だけでなく、計算を実行するときにそれらのバイトがどのように解釈されるかを決定することを理解しています。

たとえば、これらの2つの宣言はそれぞれ1バイトを割り当て、両方のバイトについて、可能な8ビットシーケンスはすべて有効な値です。

signed char s;
unsigned char u;

ただし、同じバイナリシーケンスの解釈が異なる場合があります。 11111111は、sに割り当てられた場合は-1を意味しますが、uに割り当てられた場合は255を意味します。符号付き変数と符号なし変数が同じ計算に関与する場合、コンパイラーは(大部分)適切な変換を処理します。

私の理解では、エンディアンネスは同じ原則のバリエーションに過ぎません。それは、格納されるメモリに関するコンパイル時の情報に基づいたバイナリパターンの異なる解釈です。

低レベルのプログラミングを可能にする型付き言語でその機能を使用することは明らかです。ただし、これはC、C++、または私が知っている他の言語の一部ではなく、このオンラインに関する議論は見つかりませんでした。

更新

私は尋ねてから最初の1時間で得た多くのコメントからいくつかの要点を要約しようとします:

  1. 符号付きは厳密にバイナリ(符号付きまたは符号なし)であり、エンディアンとは対照的に、常に2つの有名なバリアント(大小)がありますが、混合/ミドルエンディアンなどのあまり知られていないバリアントもあります。将来、新しいバリアントが発明される可能性があります。
  2. 複数バイトの値をバイト単位でアクセスするときは、エンディアンが重要です。マルチバイト構造のメモリレイアウトに影響するのは、エンディアン以外の多くの側面があるため、この種のアクセスはほとんどお勧めできません。
  3. C++は、 abstract machine をターゲットとし、実装に関する仮定の数を最小限に抑えることを目指しています。この抽象マシンには、エンディアンネスanyがありません。

また、符号とエンディアンが完全なアナロジーではないことを認識しています。

  • エンディアンネスはhowを定義するだけですが、何かはバイナリシーケンスとして表されますが、現在は何ができるか代表。 big intlittle intの両方の値の範囲はまったく同じです。
  • signednessは、howビットと実際の値が相互にマッピングすることを定義しますが、何ができるかにも影響します表される、例えば-3はunsigned charで表すことはできません(charが8ビットであると想定)130はsigned charで表すことができません。

そのため、一部の変数のエンディアンネスを変更しても、プログラムの動作は変更されません(バイト単位のアクセスを除く)が、通常、符号付きの変更は変更されます。

58
Lena Schimmel

標準が言うこと

[intro.abstract]/1

このドキュメントのセマンティック記述は、パラメータ化された非決定的抽象マシンを定義します。このドキュメントでは、準拠する実装の構造に要件はありません。特に、抽象マシンの構造をコピーまたはエミュレートする必要はありません。むしろ、以下で説明するように、抽象マシンの観察可能な動作をエミュレートする(のみ)には、適合実装が必要です。

C++は、エンディアンネスの概念がないため、エンディアンネス修飾子を定義できませんでした。

討論

サインとエンディアンの違いについて、OPは書きました

私の理解では、エンディアンネスは同じ原則[(signness)]の単なるバリエーションです。つまり、格納されるメモリに関するコンパイル時の情報に基づくバイナリパターンの異なる解釈です。

私は、サインには意味的側面と代表的側面の両方があると主張します1。何 [intro.abstract]/1は、C++がsemanticのみを考慮し、メモリ内の符号付き数値の表現方法に対処しないことを意味します。2。実際、 "符号ビット"はC++仕様に1回だけ表示されます であり、実装定義の値を参照します。
一方、エンディアンネスには代表的な側面しかありません。エンディアンネスには意味がありません

C++ 20では、 std::endian が表示されます。まだ実装定義ですが、 未定義の動作に基づく古いトリック に依存せずにホストのエンディアンをテストしましょう。


1) 意味論的側面:符号付き整数はゼロ未満の値を表すことができます。代表的な側面:たとえば、正/負の符号を伝えるためにビットを予約する必要があります。
2) 同様に、C++は浮動小数点数の表現方法を記述しません。IEEE-754がよく使用されますが、これは実装によって行われた選択であり、いずれの場合も標準によって強制されます。 [basic.fundamental]/8 "浮動小数点型の値表現は実装定義です"

53
YSC

YSCの答えに加えて、サンプルコードを取り上げ、それが何を達成することを目指しているかを考えてみましょう。

_struct foo {
    little int x;   // little-endian
    big long int y; // big-endian
    short z;        // native endianness
};
_

これにより、アーキテクチャに依存しないデータ交換(ファイル、ネットワークなど)のレイアウトが正確に指定されることを期待できます。

しかし、いくつかのことはまだ指定されていないため、これはおそらく動作しません。

  • データ型のサイズ:_little int32_t_、_big int64_t_、_int16_t_を使用する必要があります(必要な場合)
  • 言語内で厳密に制御できないパディングとアライメント:_#pragma_または__attribute__((packed))またはその他のコンパイラ固有の拡張機能を使用します
  • 実際の形式(1または2の補数の符号付き、浮動小数点型のレイアウト、トラップ表現)

あるいは、単に指定されたハードウェアのエンディアンを反映したい場合がありますが、biglittleはここですべての可能性をカバーするわけではありません(最も一般的な2つのみ)。

そのため、この提案は不完全で(すべての合理的なバイト順序の配置を区別するわけではありません)、効果がありません(意図したものを達成できません)。

  • 性能

    ネイティブのバイト順序から変数のエンディアンを変更すると、算術や比較などが無効になります(ハードウェアcannotがこのタイプで正しく実行されるため)、または暗黙的に追加のコードを挿入して、ネイティブに順序付けられたテンポラリを作成する必要があります作業する。

    ここでの引数は、ネイティブのバイト順への/からの手動変換が高速であるということではなく、明示的に制御することで不要な変換の数を最小限に抑えやすくなり、コードが変換が暗黙的である場合よりも動作します。

  • 複雑

    オーバーロードまたは整数型に特化したものはすべて、非ネイティブエンディアン値が渡されるというまれなイベントに対処するために、2倍のバージョンが必要になりました。それが単なる転送ラッパー(ネイティブの順序との間で変換するためのキャストを2つ含む)であっても、それは認識できるほどのメリットがない多くのコードです。

これをサポートするために言語を変更することに対する最後の議論は、コードで簡単にそれを行うことができるということです。言語の構文を変更することは大したことであり、型ラッパーのようなものよりも明らかな利点はありません。

_// store T with reversed byte order
template <typename T>
class Reversed {
    T val_;
    static T reverse(T); // platform-specific implementation
public:
    explicit Reversed(T t) : val_(reverse(t)) {}
    Reversed(Reversed const &other) : val_(other.val_) {}
    // assignment, move, arithmetic, comparison etc. etc.
    operator T () const { return reverse(val_); }
};
_
37
Useless

(数学的な概念としての)整数には、正数と負数の概念があります。この抽象的な記号の概念には、ハードウェアにさまざまな実装があります。

エンディアンネスは数学的概念ではありません。リトルエンディアンは、16ビットまたは32ビットのレジスタと8ビットのメモリバスを備えたマイクロプロセッサで、マルチバイトの2の補数の整数演算のパフォーマンスを向上させるハードウェア実装のトリックです。その作成には、ビッグエンディアンという用語を使用して、レジスタとメモリで同じバイト順序を持つ他のすべてを記述する必要がありました。

C抽象マシンには、詳細のない符号付き整数と符号なし整数の概念が含まれています。2の補数演算、8ビットバイト、またはメモリに2進数を格納する方法は必要ありません。

PS:ネット上またはメモリ/ストレージ内のバイナリデータの互換性はPIAであることに同意します。

3
Chad Farmer

それは良い質問であり、私はしばしばこのような何かが役に立つと思っていました。ただし、Cはプラットフォームの独立性を目的としているため、このような構造が基本的なメモリレイアウトに変換される場合にのみエンディアンが重要であることに注意する必要があります。この変換は、たとえばuint8_tバッファーをintにキャストしたときに発生する可能性があります。エンディアン修飾子はきれいに見えますが、プログラマはintサイズや構造体のアライメントやパッキングなど、他のプラットフォームの違いを考慮する必要があります。防衛的プログラミングの場合、メモリバッファーでの変数または構造の表現方法を詳細に制御したい場合は、明示的な変換関数をコーディングしてから、コンパイラーオプティマイザーに、サポートされている各プラットフォームで最も効率的なコードを生成させることが最善です。

2
D Dowling

エンディアンは本質的にデータ型の一部ではなく、ストレージレイアウトの一部です。

そのため、実際には符号付き/符号なしではなく、構造体のビットフィールド幅に似ています。それらと同様に、バイナリAPIの定義に使用できます。

だからあなたは次のようなものを持っているだろう

int ip : big 32;

ストレージレイアウトと整数サイズの両方を定義し、フィールドの使用とそのアクセスを一致させる最適なジョブを実行するためにコンパイラに任せます。許可された宣言がどうあるべきかは私には明らかではありません。

2
user9026597

短い答え:intを含む算術式(オーバーロードされた演算子なし)でオブジェクトを使用できない場合、これらのオブジェクトは整数型であってはなりません。また、同じ式でビッグエンディアンとリトルエンディアンの加算と乗算を許可しても意味がありません。

より長い回答:

誰かが述べたように、エンディアンはプロセッサ固有です。これは、機械語で数値として(アドレスとして、および算術演算のオペランド/結果として)使用される場合、これが数値の表現方法であることを本当に意味します。

同じことがサイネージにも当てはまります。しかし、同じ程度ではありません。言語セマンティックサイネージからプロセッサで受け入れられるサイネージへの変換は、数字を数字として使用するために行う必要があるものです。ビッグエンディアンからリトルエンディアンへの変換および逆変換は、数値をデータとして使用するために行う必要があるものです(ネットワーク経由で送信するか、ペイロード長などネットワーク経由で送信されるデータに関するメタデータを表します)。

そうは言っても、この決定は主にユースケースに基づいているようです。逆に、特定のユースケースを無視するための実用的な理由があるということです。プラグマティズムは、エンディアン変換がほとんどの算術演算よりも高価であるという事実から生じます。

言語に数値をリトルエンディアンとして保持するためのセマンティクスがある場合、開発者は、多くの算術を行うプログラムで数値のリトルエンディアンを強制することにより、自分自身を足で撃つことができます。リトルエンディアンのマシンで開発された場合、このエンディアンネスの強制は無操作になります。しかし、ビッグエンディアンのマシンに移植すると、予想外のスローダウンが多く発生します。また、問題の変数が算術演算とネットワークデータの両方に使用された場合、コードは完全に移植不可能になります。

これらのエンディアンセマンティクスを持たないか、明示的にコンパイラ固有のものにすることを強制しないと、開発者はネットワークフォーマットとの間で数値を「読み取り」または「書き込み」と見なす精神的なステップを踏むことになります。これにより、算術演算の最中にネットワークとホストのバイト順を相互に変換するコードが作成され、面倒で、怠zyな開発者による好ましい記述方法ではなくなります。

また、開発は人間の努力であるため、悪い選択を不快にすることはGood Thing(TM)です。

編集:これがうまくいかない例です:Assumethat _little_endian_int32_ and _big_endian_int32_タイプが導入されました。その場合、little_endian_int32(7) % big_endian_int32(5)は定数式です。その結果は何ですか?数値は暗黙的にネイティブ形式に変換されますか?そうでない場合、結果のタイプは何ですか?さらに悪いことに、結果の値は何ですか(この場合、おそらくすべてのマシンで同じになるはずです)?

繰り返しますが、マルチバイト数がプレーンデータとして使用される場合、char配列も同様に適切です。 「ポート」(実際にはテーブルまたはそのハッシュへのルックアップ値)であっても、整数型(計算を実行できる)ではなく、単なるバイトシーケンスです。

明示的なエンディアン数で許可されている算術演算を、ポインター型で許可されている演算のみに制限すると、予測可能性が向上する可能性があります。ビッグエンディアンのマシンでmyPortが_myPort + 5_のようなものとして宣言されていても、_little_endian_int16_は実際に意味をなします。 _lastPortInRange - firstPortInRange + 1_についても同じです。算術演算がポインター型の場合と同じように機能する場合、これは期待どおりに動作しますが、_firstPort * 10000_は不正です。

そして、もちろん、機能の肥大化が何らかの利益によって正当化されるかどうかの議論に入ります。

2

Stack Overflowを検索する実用的なプログラマーの観点から、この質問の精神はユーティリティライブラリで答えられることに注意する価値があります。 Boostには次のようなライブラリがあります。

http://www.boost.org/doc/libs/1_65_1/libs/endian/doc/index.html

議論中の言語機能に最もよく似たライブラリの機能は、big_int16_tなどの算術型のセットです。

1
Peter

誰もそれを標準に追加することを提案していないため、および/またはコンパイラの実装者がその必要性を感じたことがないためです。

たぶんあなたはそれを委員会に提案することができます。コンパイラに実装することは難しいとは思いません。コンパイラは、ターゲットマシンの基本型ではない基本型を既に提案しています。

C++の開発は、すべてのC++コーダーの仕事です。

@Schimmel。現状を正当化する人々の話を聞かないでください!この不在を正当化するために引用された議論はすべて、壊れやすい以上のものです。学生の論理学者は、コンピューターサイエンスについて何も知らなくても矛盾を見つけることができました。提案するだけで、病的な保守主義者は気にしないでください。 (アドバイス:unsignedおよびsignedキーワードは間違いと見なされるため、修飾子ではなく新しい型を提案してください)。

0
Oliv