web-dev-qa-db-ja.com

C言語でポインタが保持するデータの「タイプ」は何ですか?

ポインタがアドレスを保持していることは知っています。ポインタの型は、ポインタが指すデータの「型」に基づいて「一般的に」知られていることを知っています。ただし、ポインタは依然として変数であり、ポインタが保持するアドレスにはデータの「タイプ」が必要です。私の情報によると、アドレスは16進数形式です。しかし、私はまだ、データのどの「タイプ」がこの16進数であるのかわかりません。 (16進数が何であるかは知っていますが、10CBA20、たとえば、この文字列ですか?整数?何?アドレスにアクセスしてそれ自体を操作する場合、そのタイプを知る必要があります。これが私が求めている理由です。)

30
Gold_Sky

ポインター変数の型は..ポインターです。

Cで正式に許可されている操作は、それを(他のポインターと比較するか、特別なNULL /ゼロ値と比較する)、整数を加算または減算する、または他のポインターにキャストすることです。

未定義の動作を受け入れると、実際の値を確認できます。これは通常、整数と同じ種類のマシンワードであり、通常、整数型との間でロスレスキャストできます。 (かなり多くのWindowsコードが、DWORDまたはHANDLE typedefのポインターを非表示にすることによってこれを行います)。

メモリがフラットでないためにポインタが単純ではないアーキテクチャがいくつかあります。 DOS/8086 'near'および 'far'; PICの異なるメモリおよびコード空間。

64
pjc50

あなたは物事を複雑にしています。

アドレスは整数のピリオドです。理想的には、それらは参照されるメモリセルの数です(実際には、これはセグメント、仮想メモリなどのために、より複雑になります)。

16進構文は、プログラマーの便宜のためにのみ存在する完全なフィクションです。 0x1Aと26はまったく同じタイプのまったく同じタイプであり、どちらもコンピューターが使用するものではありません-内部的に、コンピューターは常に00011010(一連のバイナリ信号)を使用します。

コンパイラーが数値としてポインターをtreatできるかどうかは、言語の定義に依存します。「システムプログラミング」言語は、物事がどのように機能するかについて、従来より透過的です一方、「高水準」言語はプログラマからベアメタルを隠そうとしますが、これはポインタが数値であり、通常、最も一般的なタイプの数値(ビット数が最大の数値)であるという事実については何も変わりませんプロセッサアーキテクチャ)。

44
Kilian Foth

ポインターはまさにそれです-ポインター。それは他のものではありません。それが別のことだと考えようとしないでください。

C、C++、Objective-Cなどの言語では、データポインターには4種類の可能な値があります。

  1. ポインターは、オブジェクトのアドレスにすることができます。
  2. ポインタは、配列の最後の要素の直後を指すことができます。
  3. ポインターはnullポインターにすることができます。つまり、何も指していません。
  4. ポインターは不確定な値を持つ可能性があります。つまり、それはごみであり、使用しようとすると何でも(悪いことを含めて)起こる可能性があります。

関数を識別する関数ポインタ、null関数ポインタ、または値が不定の関数ポインタもあります。

その他のポインターは、C++では「メンバーへのポインター」です。これらは間違いなくnotメモリアドレスです!代わりに、クラスのanyインスタンスのメンバーを識別します。 Objective-Cには、「特定のメソッド名と引数名を持つインスタンスメソッドへのポインター」のようなセレクターがあります。メンバーポインターのように、同じように見える限り、すべてのクラスのallメソッドを識別します。

特定のコンパイラーがポインターをどのように実装するかを調査できますが、それはまったく別の問題です。

16
gnasher729

ポインタは、RAM内のストレージのワードをアドレス指定する(読み取りまたは書き込みの目的で一意に識別する)ビットパターンです。歴史的および従来の理由により、更新の単位は8ビットであり、英語では「バイト」またはフランス語では、より論理的にはオクテットとして知られています。これはユビキタスですが、固有ではありません。他のサイズが存在しました。

私の記憶が正しければ、29ビットWordを使用するコンピュータが1台ありました。これは2の累乗ではないだけでなく、素数です。これはSILLIACだと思いましたが、関連するウィキペディアの記事ではこれをサポートしていません。 CAN BUSは29ビットアドレスを使用しますが、慣例により、ネットワークアドレスは機能的に同一であっても、ポインターと呼ばれません。

人々はポインタが整数であると主張し続けます。これは本質的でも本質的でもありませんが、interpret整数としてビットパターンを使用すると、順序性の有用な品質が現れ、「文字列」や「アレイ"。連続した記憶の概念は序数の隣接関係に依存し、相対的な配置が可能です。整数比較と算術演算は意味のある形で適用できます。このため、ストレージアドレス指定のワードサイズとALU(整数演算を行うもの)の間には、ほぼ常に強い相関があります。

2つが対応しない場合があります。初期のPCでは、アドレスバスは24ビット幅でした。

9
Peter Wone

基本的に、すべての現代のコンピューターはビットプッシュマシンです。通常は、バイト、ワード、dword、またはqwordと呼ばれるデータのクラスター内でビットを押し出します。

1バイトは、8ビット、ワード2バイト(または16ビット)、dword 2ワード(または32ビット)、およびqword 2 dword(または64ビット)で構成されます。これらは、ビットを配置する唯一の方法ではありません。多くの場合SIMD命令では、128ビットおよび256ビットの操作も行われます。

アセンブリ命令は、通常、レジスタとメモリアドレスで動作し、上記のいずれかの形式で動作します。

ALU(算術論理演算ユニット)は、整数(通常は2の補数形式)を表すかのようにビットのバンドルを操作し、浮動小数点値(通常はIEEE 754スタイルのfloatおよびdouble)。他のパーツは、ある形式、文字、テーブルエントリ、CPU命令、またはアドレスのバンドルデータであるかのように動作します。

一般的な64ビットコンピューターでは、8バイト(64ビット)のバンドルがアドレスです。通常、これらのアドレスは16進形式(0xabcd1234cdef5678など)で表示されますが、これは人間がビットパターンを読み取る簡単な方法です。各バイト(8ビット)は2つの16進文字として書き込まれます(各16進文字-0からF-は4ビットを表します)。

実際に起こっていること(実際にはある程度のレベル)には、通常レジスタに格納されているか、メモリバンクの隣接する場所に格納されているビットがあり、それらを別の人間に説明しようとしているだけです。

ポインタをたどるのは、メモリコントローラにその場所のデータを提供するように要求することです。通常、メモリコントローラーに特定の場所(まあ、暗黙的に場所の範囲、通常は隣接しています)で特定のバイト数を要求します。これは、私が得ないさまざまなメカニズムを通じて配信されます。

コードは通常、データのフェッチ先(レジスタ、別のメモリアドレスなど)を指定します。通常、整数を期待して浮動小数点データをレジスタにロードすること、またはその逆を行うことはお勧めできません。

C/C++のデータのタイプは、コンパイラーが追跡するものであり、生成されるコードを変更します。通常、それを作るデータには本質的なものは何もありません実際に任意の1つのタイプ。コードによって整数のような方法(または浮動小数点のような方法、またはアドレスのような方法)で操作される(バイトに配置された)ビットのコレクション。

これには例外があります。特定のものが異なる種類ビットのアーキテクチャーがあります。最も一般的な例は保護された実行ページです-CPUに何をするかを指示する命令はビットですが、実行時に実行するコードを含む(メモリ)ページは特別にマークされ、変更できず、マークされていないページを実行できません実行ページとして。

読み取り専用のデータ(ROMに物理的に書き込むことができない場合もあります!)、整列の問題(一部のプロセッサは、特に整列しない限り、メモリからdoublesをロードできない)もあります。方法、または特定のアラインメントを必要とするSIMD命令)、および他の無数のアーキテクチャの癖。

上記の詳細レベルでさえ嘘です。コンピュータはビットを「実際に」押し回しているのではなく、実際に電圧と電流を押し回しています。これらの電圧と電流は、ビットの抽象化レベルで「想定」されていることを実行しない場合があります。チップは、そのようなエラーのほとんどを検出して修正するように設計されており、上位レベルの抽象化がそれを認識する必要はありません。

それも嘘です。

抽象化の各レベルは下のレベルを隠し、"Hello World"を出力するためにファインマン図を覚える必要なく、問題の解決を考えることができます。

したがって、十分な誠実さのレベルで、コンピュータはビットをプッシュし、それらのビットは、それらがどのように使用されるかによって意味が与えられます。

6
Yakk

人々はポインタが整数であるかどうかについて大いに議論しました。これらの質問に対する答えは実際にあります。ただし、仕様の領域に一歩踏み込む必要があります。これは、気弱な人には向いていません。 C仕様 ISO/IEC 9899:TC2 を見てみましょう。

6.3.2.3ポインタ

  1. 整数は任意のポインタ型に変換できます。以前に指定された場合を除き、結果は実装定義であり、正しく整列されていない可能性があり、参照されたタイプのエンティティをポイントしていない可能性があり、トラップ表現である可能性があります。

  2. ポインタ型は整数型に変換できます。前述の場合を除き、結果は実装定義です。結果を整数型で表現できない場合の動作は未定義です。結果は、整数型の値の範囲内である必要はありません。

これを行うには、いくつかの一般的な仕様の用語を知る必要があります。 「実装が定義されている」とは、すべてのコンパイラがそれを異なる方法で定義できることを意味します。実際、コンパイラーは、コンパイラーの設定に応じてさまざまな方法で定義することもできます。未定義の動作とは、コンパイル時エラーの発生から説明できない動作まで、コンパイラが完全に何でもできることを意味します。

これから、基になるストレージフォームが指定されていないことがわかります。それ以外は、整数型への変換mayが可能です。正直なところ、事実上、Sunの下のすべてのコンパイラは、ボンネットの下のポインタを整数アドレスとして表します(1つではなく2つの整数として表される場合があるいくつかの特別な場合)が、仕様では、 10文字の文字列としてアドレス!

Cから早送りしてC++仕様を見ると、reinterpret_castでもう少し明確になりますが、これは異なる言語であるため、その値はさまざまです。

ISO/IEC N337:C++ 11ドラフト仕様(手元にあるのはドラフトのみです)

5.2.10キャストを再解釈する

  1. ポインターは、それを保持するのに十分な大きさの整数型に明示的に変換できます。マッピング関数は実装定義です。 [注:基盤となるマシンのアドレス指定構造を知っている人にとっては、当然のことです。 —end note] std :: nullptr_t型の値は、整数型に変換できます。この変換の意味と有効性は、(void *)0から整数型への変換と同じです。 [注:reinterpret_castを使用して、型の値を型std :: nullptr_tに変換することはできません。 —エンドノート]

  2. 整数型または列挙型の値は、明示的にポインターに変換できます。十分なサイズの整数に変換されたポインター(実装に存在する場合)と同じポインター型に戻るポインターは、元の値になります。ポインタと整数の間のマッピングは、それ以外の場合は実装定義です。 [注:3.7.4.3で説明されている場合を除き、このような変換の結果は安全に派生したポインター値にはなりません。 —エンドノート]

ここでわかるように、数年後、C++は整数へのマッピングが存在すると想定しても安全であることを発見しました。そのため、未定義の動作についての話はありません(ただし、パート4と4の間には興味深い矛盾があります)。 「実装にそのようなものが存在する場合は」という表現を含む5)


これからあなたは何を取り除くべきですか?

  • ポインタの正確な表現は実装定義です。 (実際には、混乱させるために、一部の小さな組み込みコンピュータは、アドレスエイリアスをサポートするためにアドレス255としてnullポインタ(void)0を表します彼らが使うトリック)*
  • メモリ内のポインタの表現について質問する必要がある場合は、プログラミングのキャリアの中で、ポインタをいじりたいと思っている段階ではないでしょう。

最善の策:(char *)へのキャスト。 CとC++の仕様は、配列と構造体のパッキングを指定する規則でいっぱいであり、両方とも常にchar *へのポインターのキャストを許可します。 charは常に1バイトです(Cでは保証されていませんが、C++ 11によって言語の必須部分になっているため、どこでも1バイトであると想定しても比較的安全です)。これにより、ポインターの実装固有の表現を実際に知る必要なく、バイト単位のレベルでポインター演算を行うことができます。

3
Cort Ammon

ほとんどのアーキテクチャーでは、ポインターがマシンコードに変換されると、ポインターのタイプは存在しなくなります(「ファットポインター」を除く)。したがって、intへのポインターとdoubleへのポインターは、少なくともそれ自体では区別できません。*

[*]ただし、適用する操作の種類に基づいて推測することはできます。

1
Rufflewind

CおよびC++について理解する重要なことは、実際の型とは何かです。それらが実際に行うことは、ビット/バイトのセットを解釈する方法をコンパイラーに示すことです。次のコードから始めましょう:

int var = -1337;

アーキテクチャに応じて、整数には通常、その値を格納するための32ビットのスペースが与えられます。つまり、varが格納されているメモリ内のスペースは、「11111111 11111111 11111010 11000111」または16進数の「0xFFFFFAC7」のようになります。それでおしまい。その場所に保存されるのはそれだけです。すべての型は、その情報を解釈する方法をコンパイラーに伝えます。ポインタも同じです。私がこのようなことをした場合:

int* var_ptr = &var;   //the ampersand is telling C "get the address where var's value is located"

次に、コンパイラはvarの場所を取得し、最初のコードスニペットが値-1337を保存するのと同じ方法でそのアドレスを格納します。保存方法に違いはなく、使用方法に違いはありません。 var_ptrをintへのポインターにしたことは問題ではありません。必要に応じて、それを行うことができます。

unsigned int var2 = *(unsigned int*)var_ptr;

これにより、varの上記の16進数値(0xFFFFFAC7)が、var2の値を格納する場所にコピーされます。次にvar2を使用すると、値は4294965959になります。var2のバイトはvarと同じですが、数値が異なります。これらのビットはunsigned longを表すと伝えたため、コンパイラーはそれらを異なる方法で解釈しました。ポインター値についても同じことができます。

unsigned int var3 = (unsigned int)var_ptr;

この例では、varのアドレスを表す値をunsigned intとして解釈することになります。

うまくいけば、これはあなたのために物事を明確にし、Cがどのように機能するかについてより良い洞察を与えるでしょう。あなたがSHOULD NOTを実際の製品コードの以下の2行で行ったクレイジーなことを行うことに注意してください。それはデモンストレーションのためだけでした。

1
NULL

タイプを組み合わせます。

特に、特定の型は、プレースホルダーでパラメーター化されているかのように組み合わされます。配列型とポインタ型は次のとおりです。そのようなプレースホルダーが1つあります。これは、それぞれ、配列の要素のタイプまたはポイントされているもののタイプです。関数タイプもこのようなものです。これらには、パラメーター用の複数のプレースホルダーと、戻り値の型用のプレースホルダーを含めることができます。

Charへのポインタを保持するように宣言された変数は、「pointer to char」型です。 intへのポインターへのポインターを保持するように宣言されている変数のタイプは、「pointer to pointer to int」です。

型「の値」「intへのポインター」は、逆参照操作によって「intへのポインター」に変更できます。したがって、型の概念は単語だけでなく、数学的に重要な構造であり、型の値を使用して何ができるかを示します(逆参照、パラメータとして渡す、変数に割り当てるなど)。また、サイズ(バイト数)も決定します索引付け、算術、および増分/減分操作)。

追伸タイプについてさらに深く知りたい場合は、次のブログを試してください。 http://www.goodmath.org/blog/2015/05/13/expressions-and-arity-part-1/

1
Erik Eidt

整数。

コンピューターのアドレス空間には、0から順に1ずつ増加する番号が付けられています。そのため、ポインターはアドレス空間のアドレスに対応する整数を保持します。

1
galois