サイズ、インデックスなどの場合はsize_tまたはint


15

C ++では、size_t(または、より正確にはT::size_type「通常」であるsize_t、すなわち、unsignedタイプ)の戻り値として使用されsize()、引数operator[]等、(参照std::vector、ら。)。

一方、.NET言語は同じ目的でint(および、オプションでlong)を使用します。実際、CLS準拠の言語は、符号なしの型をサポートする必要はありません

.NETがC ++よりも新しいことを考えると、配列インデックスや長さのように「おそらく」負になれないものでも使用に問題があるかもしれunsigned intないということがわかります。後方互換性のためのC ++アプローチは「歴史的なアーティファクト」ですか?または、2つのアプローチの間に実際の重要な設計上のトレードオフがありますか?

なぜこれが重要なのですか?さて... C ++の新しい多次元クラスには何を使うべきですか。size_tまたはint

struct Foo final // e.g., image, matrix, etc.
{
    typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
    typedef size_t size_type; // c.f., std::vector<>

    dimension_type bar_; // maybe rows, or x
    dimension_type baz_; // e.g., columns, or y

    size_type size() const { ... } // STL-like interface
};

6
注目に値する:.NET Frameworkのいくつかの場所で、-1インデックスを返す関数から返され、「見つかりません」または「範囲外」を示します。Compare()関数からも返されます(実装IComparable)。32ビットのintは、一般的な数値のgo to typeと見なされます。理由は明らかです。
ロバートハーベイ

回答:


9

.NETがC ++よりも新しいことを考えると、配列インデックスや長さのように負の値を「取り得ない」場合でも、unsigned intを使用すると問題が発生する可能性があることがわかります。

はい。画像処理や配列処理などの特定のタイプのアプリケーションでは、多くの場合、現在の位置に関連する要素にアクセスする必要があります。

sum = data[k - 2] + data[k - 1] + data[k] + data[k + 1] + ...

これらのタイプのアプリケーションでは、慎重に考えることなく、符号なし整数で範囲チェックを実行できません。

if (k - 2 < 0) {
    throw std::out_of_range("will never be thrown"); 
}

if (k < 2) {
    throw std::out_of_range("will be thrown"); 
}

if (k < 2uL) {
    throw std::out_of_range("will be thrown, without signedness ambiguity"); 
}

代わりに、範囲チェック式を再配置する必要があります。それが主な違いです。プログラマは整数変換規則も覚えておく必要があります。疑わしい場合は、http://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversionsを再度お読みください

多くのアプリケーションでは、非常に大きな配列インデックスを使用する必要はありませんが、範囲チェックを実行する必要があります。さらに、多くのプログラマーは、この表現の再編成体操を行うように訓練されていません。1つの機会を逃すと、悪用の扉が開きます。

実際、C#は、配列ごとに2 ^ 31を超える要素を必要としないアプリケーション向けに設計されています。たとえば、スプレッドシートアプリケーションでは、その数の行、列、またはセルを処理する必要はありません。C#は、コンパイラオプションを台無しにせずにキーワードを使用してコードのブロックに対して有効にできるオプションのチェックされた演算を行うことにより、上限を処理します。このため、C#は符号付き整数の使用を好みます。これらの決定がすべて考慮される場合、それは理にかなっています。

C ++は単純に異なり、正しいコードを取得するのが困難です。

「最小の原理」の潜在的な違反を取り除くために符号付き算術を許可することの実際的な重要性に関して、適切なケースはOpenCVです。処理は、相対配列インデックスを多用するプログラミングドメインの例です。符号なし整数アンダーフロー(負の結果がラップされる)は、アルゴリズムの実装を大幅に複雑にします。


これはまさに私の状況です。特定の例に感謝します。(はい、私はこれを知っていますが、引用するために「より高い権限」を持っていることは有用である場合があります。)
16

1
@Dan:引用する必要がある場合は、この投稿の方が良いでしょう。
rwong

1
@Dan:John Regehrは、プログラミング言語でこの問題を積極的に調査しています。参照してくださいblog.regehr.org/archives/1401
rwong

逆張りの意見がありますgustedt.wordpress.com/2013/07/15/...
rwong

14

この答えは、誰がコードを使用するのか、そしてどの標準を見たいのかによって異なります。

size_t 目的のある整数サイズです:

この型size_tは、任意のオブジェクトのバイト単位のサイズを格納するのに十分な大きさの、実装で定義された符号なし整数型です。(C ++ 11仕様18.2.6)

したがって、オブジェクトのサイズをバイト単位で処理する場合は常にを使用する必要がありますsize_t。現在、多くの場合、これらのディメンション/インデックスを使用してバイトをカウントしていませんが、ほとんどの開発者size_tは一貫性のためにそれらを使用することを選択しています。

クラスがSTLクラスのルックアンドフィールを持つことを目的としている場合は、常に使用する必要あることに注意してくださいsize_t。仕様のすべてのSTLクラスはを使用しますsize_t コンパイラがtypedef size_tするのは有効であり、にtypedef することunsigned intも有効ですunsigned longintまたはlong直接使用すると、最終的にコンパイラーに遭遇します。このコンパイラーでは、標準に従わなかったために、STLのスタイルに従っていると考えている人が閉じ込められます。

署名された型の使用に関しては、いくつかの利点があります。

  • 短い名前-人々が入力するのは本当に簡単ですintが、コードを乱雑にするのははるかに困難unsigned intです。
  • サイズごとに1つの整数-32ビットのCLS準拠整数は1つだけで、Int32です。C ++には、2つ(int32_tuint32_t)があります。これにより、APIの相互運用性がより簡単になります。

署名された型の大きな欠点は明らかです。ドメインの半分が失われます。符号付きの数は、符号なしの数とは限りません。C / C ++が登場したとき、これは非常に重要でした。プロセッサの全機能に対応できる必要があり、そのためには符号なしの数値を使用する必要がありました。

対象となる.NETの種類のアプリケーションでは、フルドメインの符号なしインデックスの必要性はそれほど強くありませんでした。このような数値の目的の多くは、マネージ言語では単純に無効です(メモリプーリングが思い浮かびます)。また、.NETが登場したとき、64ビットコンピューターは明らかに未来でした。私たちは64ビット整数の全範囲を必要とすることから遠く離れているので、1ビットを犠牲にすることは以前ほど苦痛ではありません。本当に40億個のインデックスが必要な場合は、64ビット整数の使用に切り替えるだけです。最悪の場合、32ビットマシンで実行すると少し遅くなります。

私は貿易を便利なものの一つと考えています。あなたはあなたが決して史上-everが使用することをお使いのインデックスタイプのビットを無駄に気にしないことに十分な計算力を持ってしまった場合、それはただのタイプに便利ですintlong、そこから徒歩。最後のビットが本当に欲しいと思ったら、おそらく数字の符号に注意を払うべきだったでしょう。


size()was の実装だとしましょうreturn bar_ * baz_;。それは今、私が使用しなかった場合は持っていない整数オーバーフロー(ラップアラウンド)の潜在的な問題を作成しませんsize_tか?
16

5
@Dan符号なしintを持つことが重要な場合のようなケースを構築できます。そのような場合、完全な言語機能を使用してそれを解決するのが最善です。ただし、bar_ * baz_符号なし整数ではなく符号付き整数をオーバーフローさせることができるクラスを作成することは興味深い構成になると言わなければなりません。C ++に限定すると、符号なしオーバーフローは仕様で定義されますが、符号付きオーバーフローは未定義の動作なので、符号なし整数のモジュロ演算が望ましい場合は、実際に定義されているため、間違いなく使用してください!
コートアンモン-復活モニカ

1
@Dan- が符号付き乗算をオーバーフローした場合、言語UBランドにいます。(そして、モードでは、次を参照してください:) それから、ほんの少しだけで、それは符号なし乗算をオーバーフローしました、ユーザーコードバグランドで-あなたは偽のサイズを返します。ですから、ここでは無署名の購入はあまりないと思います。size()fwrapv
マーティンBa

4

上記のrwong答えはすでに問題を際立たせていると思います。

002を追加します。

  • size_t、つまり、サイズ...

    任意のタイプ(配列を含む)の理論的に可能なオブジェクトの最大サイズを格納できます。

    ...はsizeof(type)==1、の場合、つまり、バイト(char)型を処理する場合の範囲インデックスにのみ必要です。(ただし、ptr型よりも小さくすることができます

  • そのためxxx::size_type、署名されたサイズのタイプであっても、99.9%のケースで使用できます。(と比較ssize_t
  • 事実std::vector選び、友人size_t符号なしのサイズとインデックスのために、タイプがされ、一部で考えられて設計上の欠陥であることを。私は同意します。(真剣に、5分かけて、CppCon 2016稲妻の話を見てください:ジョンカルブ「unsigned:A Guideline for Better Code」
  • 今日C ++ APIを設計するとき、あなたは厳しい場所にいます:size_t標準ライブラリと一貫性を保つために使用するか、(signed)を 使用するintptr_tssize_t、簡単でバグの少ないインデックス計算を使用します。
  • int32またはint64を使用しないでください。intptr_t署名したい場合に使用し、マシンワードサイズを使用するか、を使用しますssize_t

アドレスに半分以上(「インデックス」、または)のアドレス空間を必要とする理論的な問題のように直接質問に答えるために、それは、完全に「歴史的なアーティファクト」ではありませんしなければならないこと、aehm、のような低レベル言語で何とか対処C ++。

後知恵では、私は、個人的に、だと思う、それはある標準ライブラリは、符号なし使用する設計上の欠陥size_t、それは生のメモリサイズを表しますが、コレクションのような型付きデータの容量なくても場所の上にすべてを:

  • 与えられたC ++整数昇格規則 ->
  • 符号なしの型は、意味的に符号のないサイズのような「セマンティック」型の良い候補にはなりません。

ここでジョンのアドバイスを繰り返します。

  • サポートする操作のタイプを選択します(値の範囲ではありません)。(* 1)
  • APIで符号なしの型を使用しないでください。これにより、利点がなくなり、バグが隠されます。
  • 数量に「符号なし」を使用しないでください。(* 2)

(* 1)つまり、符号なし==ビットマスク、決して計算を行いません(ここで最初の例外が発生します-ラップするカウンターが必要な場合があります-これは符号なしの型でなければなりません)。

(* 2)数えたり、計算したりすることを意味する量。


「完全に利用可能なフラットメモリ」とはどういう意味ですか?また、の代わりににssize_t署名されたペンダントとして定義されていないことを確認してください。size_tintptr_t
デュプリケータ

@Deduplicator-まあ、size_t定義が少し台無しになっているかもしれませんね。size_tとintptrおよびen.cppreference.com/w/cpp/types/size_tを参照してください 。今日新しいことを学びました。:-)残りの引数は有効だと思います。使用されている型を修正できるかどうかを確認します。
マーティンBa

0

パフォーマンス上の理由から、通常はsize_tを使用して、誤った計算でアンダーフローが発生するようにします。これにより、両方の範囲チェック(ゼロ以下およびsize())を1つに減らすことができます。

署名されたintを使用:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

if (i < 0)
{
    //error
}

if (i > size())
{
    //error
}

符号なし整数を使用する:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

/// This will underflow any number below zero, so that it becomes a very big *positive* number instead.
uint32_t asUnsigned = static_cast<uint32_t>(i);

/// We now don't need to check for below zero, since an unsigned integer can only be positive.
if (asUnsigned > size())
{
    //error
}

1
あなたは本当にそれをもっと徹底的に説明したいのです。
マーティンBa

答えをより便利にするために、さまざまなコンパイラベンダーのマシンコードで整数配列の境界またはオフセット比較(符号付きおよび符号なし)がどのように見えるかを説明できます。特定のC ++コードとコンパイラフラグに対応するコンパイル済みマシンコードを表示できるオンラインC ++コンパイラと逆アセンブリサイトが多数あります。
rwong

これをもう少し説明しようとしました。
asger 16
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.