size_tとuintptr_tの比較


246

C標準でsize_tは、任意の配列インデックスを保持できる型であることを保証しています。つまり、論理的には、size_t任意のポインタ型を保持できる必要があります。これは合法であり、常に機能するはずであるとGoogleで見つけたいくつかのサイトで読んだことがあります。

void *v = malloc(10);
size_t s = (size_t) v;

そのため、C99では、標準によりintptr_tおよびuintptr_t型が導入されました。これらは、ポインタを保持できることが保証されている符号付きおよび符号なしの型です。

uintptr_t p = (size_t) v;

だから、使用しての違いは何であるsize_tとはuintptr_t?どちらも符号なしであり、どちらも任意のポインタ型を保持できるはずなので、機能的には同じように見えます。明快さ以外に、aよりもuintptr_t(またはもっと良いのはa void *)を使用する本当に説得力のある理由はありますsize_tか?フィールドが内部関数によってのみ処理される不透明な構造で、これを行わない理由はありますか?

同様に、ptrdiff_tポインタの違いを保持できる符号付きの型であり、したがってほとんどのポインタを保持できるので、それはどのように区別されintptr_tますか?

これらのタイプはすべて、基本的に同じ機能のわずかに異なるバージョンを提供しているのではないですか?そうでない場合、なぜですか?それらの1つを使用して、他のユーザーを使用してできないことは何ですか?もしそうなら、なぜC99は2つの本質的に不必要な型を言語に追加したのですか?

関数ポインターは現在の問題には当てはまらないため、無視してもかまいませんが、「正しい」答えの中心となるのではないかという疑惑があるので、遠慮なく触れてください。

回答:


236

size_t任意の配列インデックスを保持できるタイプです。つまり、論理的には、size_tは任意のポインタ型を保持できる必要があります。

必ずしも!たとえば、セグメント化された16ビットアーキテクチャの時代を振り返ってみましょう。たとえば、配列が単一のセグメントに制限される場合があります(16ビットの場合size_tはそうなります)。ただし、複数のセグメントを使用することもできます(選択するには32ビットintptr_tタイプが必要です)セグメントとその中のオフセット)。私はこれらのものが均一にアドレス可能な非セグメント化アーキテクチャの最近では奇妙に聞こえることを知っていますが、標準は「2009年の通常」よりも幅広い多様性に対応しなければなりません。


6
これは、同じ結論にジャンプし、多くの他の人と一緒に、との違いを説明size_tし、uintptr_tしかし、何についてptrdiff_tとをintptr_t-これらの両方は、ほぼすべてのプラットフォーム上で同じ値の範囲を保存することはできないでしょうか?特に、ptrdiff_t既に符号付きポインターサイズの整数型の目的を果たしている場合に、符号付きと符号なしの両方のポインターサイズの整数型がある理由。
Chris Lutz、

8
ほぼすべてのプラットフォーム上」にある重要なフレーズ、@ Chris。実装は、ポインタを0xf000-0xffffの範囲に制限することは自由です。これには、16ビットのintptr_tが必要ですが、12/13ビットのptrdiff_tのみが必要です。
paxdiablo 2009

29
@Chris、同じ配列内のポインターに対してのみ、それらの違いを取ることが明確に定義されています。したがって、まったく同じセグメント化された16ビットアーキテクチャ(配列は単一のセグメント内に存在する必要がありますが、2つの異なる配列が異なるセグメントに存在する可能性があります)ポインターは4バイトである必要がありますが、ポインターの違いは2バイトです。
Alex Martelli

6
@AlexMartelli:ポインターの差が正または負になる可能性があることを除きます。規格はsize_t少なくとも16ビットである必要がありますptrdiff_tが、少なくとも17ビットである必要があります(実際には、少なくとも32ビットであることを意味します)。
キーストンプソン

3
セグメント化されたアーキテクチャーを気にしないでください。x86-64のような最新のアーキテクチャーはどうですか?このアーキテクチャの初期の実装では、48ビットのアドレス可能なスペースしか提供されませんが、ポインタ自体は64ビットのデータ型です。合理的にアドレス指定できる最大の連続するメモリブロックは48ビットであるため、SIZE_MAX2 ** 64にしないでください。これはフラットなアドレス指定を使用しています。SIZE_MAXデータポインタの範囲とデータポインタの範囲を一致させるためにセグメンテーションは必要ありません。
Andon M. Coleman

89

あなたの声明について:

「C標準でsize_tは、これが任意の配列インデックスを保持できる型であることを保証しています。つまり、論理的にsize_tは、任意のポインター型を保持できるはずです。」

これは実際には誤りです(誤った推論による誤解)(a)。後者は前者に続くと思うかもしれませんが、実際はそうではありません。

ポインタと配列インデックスは同じものではありません。配列を65536要素に制限し、ポインターが任意の値を巨大な128ビットアドレス空間にアドレス指定できるようにする準拠実装を想定することは非常に妥当です。

C99では、size_t変数の上限はによって定義されてSIZE_MAXおり、これは65535まで低くすることができます(C11で変更されていないC99 TR3、7.18.3を参照)。ポインターは、最新のシステムでこの範囲に制限されている場合、かなり制限されます。

実際には、おそらくあなたの仮定が成り立つことがわかるでしょうが、それは標準がそれを保証しているからではありません。それは実際にはそれを保証しないからです。


(a)これは何らかの形での個人的な攻撃ではなく、批判的思考の文脈でステートメントが誤っている理由を述べているだけです。たとえば、次の推論も無効です。

すべての子犬はかわいいです。これはかわいいです。したがって、これは子犬でなければなりません。

最初の2つの文子犬ではないかわいいものの存在を可能にするため、2つの事実が結論につながらないということは、ここでは子犬のかわいらしさは関係ありません。

これは最初のステートメントに似ていますが、必ずしも2番目のステートメントを必須とするわけではありません。


Alex Martelliのコメントで私が言ったことを再入力するのではなく、明確化に感謝を述べますが、質問の後半(ptrdiff_tvs.のintptr_t部分)を繰り返します。
Chris Lutz、

5
@Ivanは、ほとんどのコミュニケーションと同様に、特定の基本的な項目について共通の理解が必要です。この答えが「面白がる」と思われる場合は、私の意図を誤解していると思います。あなたが私の「論理的誤り」のコメントを参照していると仮定すると(他の可能性は見当たらない)、これは、OPを犠牲にして作成された一部のステートメントではなく、事実のステートメントとしての意味でした。(単なる一般的な苦情ではなく)誤解の可能性を最小限に抑えるための具体的な改善策を提案したい場合は、喜んで検討させていただきます。
paxdiablo

1
@ivan_pozdeev-これは不愉快で思い切った編集の組み合わせであり、paxdiabloがだれでも「面白がっている」という証拠はありません。私はOPだったら、私は....この権利バックを転がします
無から

1
@Ivanは、提案された編集に満足できず、ロールバックし、意図しない違反を削除しようとしました。他に何か変更がある場合は、チャットで始めることをお勧めします。
paxdiablo 2018年

1
@paxdiablo大丈夫、私は「これは実際には誤りである」とはあまりひいきではないと思います。
ivan_pozdeev 2018年

36

セグメントの制限、エキゾチックなアーキテクチャなどの推論については、他のすべての回答をそのまま示しておきます。

名前の単純な違いは、適切な型を適切なものに使用するのに十分な理由はありませんか?

サイズを保存する場合は、を使用しますsize_t。ポインタを保存する場合は、を使用しますintptr_t。コードを読んだ人は、「ああ、これはおそらくバイト単位のサイズです」、「ああ、なんらかの理由で、ここにポインタ値が整数として格納されている」とすぐにわかります。

それ以外の場合は、すべてを使用できますunsigned long(または、ここでは現代unsigned long long)。サイズはすべてではありません。タイプ名には意味があり、プログラムを説明するのに役立ちます。


私は同意しますが、size_tフィールドにポインタ型を格納することを含むハック/トリック(もちろん、私は明確に文書化します)を検討していました。
Chris Lutz、

@MarkAdler標準では、ポインタを完全に整数として表現できる必要はありません。どのポインタ型も整数型に変換できます。前述の場合を除き、結果は実装定義です。結果を整数型で表現できない場合の動作は未定義です。結果は、整数型の値の範囲内である必要はありません。したがって、のみvoid*intptr_tおよびuintptr_tデータへのポインタを表すことができることが保証されます。
Andrew Svietlichnyy

12

最大の配列のサイズがポインターよりも小さい可能性があります。セグメント化されたアーキテクチャを考えてみてください-ポインタは32ビットかもしれませんが、単一のセグメントは64KBしかアドレス指定できない場合があります(たとえば、古いリアルモード8086アーキテクチャ)。

これらはデスクトップマシンでは一般的に使用されていませんが、C標準は、小規模で特殊なアーキテクチャをサポートすることを目的としています。たとえば、8ビットまたは16ビットCPUを使用して開発されている組み込みシステムはまだあります。


しかし、配列のようにポインタにインデックスを付けることができるので、それsize_tを処理することもできますか?それとも、一部の遠いセグメントの動的配列は、そのセグメント内のインデックス付けに限定されますか?
Chris Lutz

インデックスポインターは、それらが指す配列のサイズに対してのみ技術的にサポートされています。したがって、配列が64KBサイズに制限されている場合、ポインター演算がサポートする必要があるのはそれだけです。ただし、MS-DOSコンパイラは「巨大な」メモリモデルをサポートしていました。この場合、メモリ全体を単一の配列としてアドレス指定できるように、遠いポインタ(32ビットのセグメント化されたポインタ)が操作されました。かなり醜い-オフセットが16(または何か)の値を超えて増加した場合、オフセットは0に折り返され、セグメント部分が増加しました。
マイケル・バー、

7
en.wikipedia.org/wiki/C_memory_model#Memory_segmentation を読んで、私たちが自由になるように亡くなったMS-DOSプログラマーのために泣いてください。
Justicle

さらに悪いことに、stdlib関数はHUGEキーワードを処理しませんでした。すべてのための16ビットMS-C strさえする機能とBorland mem関数(memsetmemcpymemmove)。これは、オフセットがオーバーフローしたときにメモリの一部を上書きできることを意味し、組み込みプラットフォームでデバッグするのが楽しかったです。
PatrickSchlüter、2010

@Justicle:8086セグメントアーキテクチャはCでは十分にサポートされていませんが、1MBのアドレススペースで十分であるが64Kのアドレススペースでは不十分な場合に、より効率的な他のアーキテクチャは知りません。最近の一部のJVMは、実際にはx86リアルモードと非常によく似たアドレス指定を使用しており、32ビットのオブジェクト参照を左に3ビットシフトして、32 GBのアドレス空間にオブジェクトベースアドレスを生成します。
スーパーキャット2015年

5

私は(そしてこれはすべての型名に当てはまります)、コードであなたの意図をよりよく伝えると想像します。

にもかかわらず、例えばunsigned shortおよびwchar_tWindows上で同じ大きさ(と思う)、使用しているwchar_t代わりにunsigned short、あなたがいうだけで、いくつかの任意の数よりワイド文字を格納するためにそれを使用するという意図を示しています。


私のシステムで、 -しかし、ここで違いがありますwchar_tよりもはるかに大きいunsigned short間の移植性の懸念に対し、誤って、深刻な(と現代)移植の問題を作成し、他のための1つを使用して、そうsize_tとはuintptr_t土地遠くにあると思われます1980年の何か(日付のランダムな突き刺しがある)
クリス・ルッツ

Touché!しかし、再び、size_tそしてuintptr_tそれらの名前の暗黙の使用はまだあります。
dreamlax 2009

彼らはそうします、そして私はこれを単に明快にする以上の動機があるかどうか知りたかったのです。そして、それはあることがわかりました。
Chris Lutz、

3

後方と前方の両方を見て、さまざまな奇妙なアーキテクチャーがランドスケープに散在していたことを思い出すと、それらはすべての既存のシステムをラップし、また可能なすべての将来のシステムを提供しようとしていると確信しています。

確かに、物事が解決する方法として、これまでそれほど多くのタイプを必要としていませんでした。

しかし、かなり一般的なパラダイムであるLP64でさえ、システムコールインターフェイスにsize_tとssize_tが必要でした。完全な64ビットタイプを使用するとコストが高く、4 GBを超えるI / O演算をパントしたいが64ビットポインターを使用する、より制約のあるレガシーまたは将来のシステムを想像できます。

私はあなたが不思議に思う必要があると思います:何が開発されたのか、将来何が起こるのか。(おそらく128ビットの分散システムのインターネット全体のポインターですが、システムコールでは64ビット以下、あるいは「レガシー」32ビットの制限でさえあります。:-)レガシーシステムが新しいCコンパイラを取得する可能性があるというイメージ。 。

また、当時存在していたものも見てください。ジリオン286リアルモードメモリモデルに加えて、CDC 60ビットワード/ 18ビットポインターメインフレームはどうですか?クレイシリーズはどうですか?通常のILP64、LP64、LLP64を気にしないでください。(マイクロソフトはLLP64を使用していると思っていましたが、P64でなければなりませんでした。)委員会がすべてのベースをカバーしようとしていることは確かに想像できます...


-9
int main(){
  int a[4]={0,1,5,3};
  int a0 = a[0];
  int a1 = *(a+1);
  int a2 = *(2+a);
  int a3 = 3[a];
  return a2;
}

intptr_tは常にsize_tの代わりに使用する必要があり、その逆も同様です。


10
このすべてが示すのは、Cの特定の構文上の癖です。配列のインデックス付けは、x [y]が*(x + y)と同等であるという点で定義されています。a+ 3と3 + aはタイプと値が同じであるため、 3 [a]またはa [3]を使用します。
Fred Nurk、2011
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.