メモリアドレスでない場合、Cポインタとは正確には何ですか?


206

Cに関する評判の良い情報源では、&演算子について説明した後で、次の情報が提供されています。

... [アドレス]という用語が残っているのは少し残念です。アドレスが何であるかを知らない人を混乱させ、誤解させます。ポインタをアドレスのように考えることは、通常、悲しみにつながります。 。

私が読んだ他の資料(同じように評判の良い情報源からのものだと思います)は、常にポインタと&演算子をメモリアドレスを与えるものとして言及していました。私は問題の現実を探り続けたいと思いますが、信頼できる情報源が同意しない場合、それは一種の困難です。

さて、少し混乱しています。正確に言えば、ポインタとは、メモリアドレスではないのでしょうか。

PS

作者は後にこう述べています...私は別のものを発明するので、しかし長期的「のアドレス」を使用していきます [用語]はさらに悪化するだろう。


118
ポインターは、アドレスを保持する変数です。また、独自のアドレスを持ってます。これは、ポインタと配列の根本的な違いです。アレイは、効果的であるアドレス(及び暗に、そのアドレスは、それ自体)。
WhozCraig 2013年

7
見積もりの​​「信頼できるソース」は何ですか?
Cornstalks 2013年

22
究極の信頼できるソースは言語標準であり、それから半派生した本や著者の尻から半抜粋した本ではありません。私は難しい方法を学びました。できる限りの間違いを犯し、ゆっくりとCのメンタルモデルを標準で記述されているものに少し近づけて構築し、最後にそのモデルを標準のモデルで置き換えました。
Alexey Frunze 2013年

9
@thang人々は、pointer = integerを考えるのが一般的です(x86 LinuxとWindowsはそのことを "教えてくれます")。プラットフォーム。同じ人々は、データへのポインタと関数へのポインタを相互に変換でき、データをコードとして実行し、コードをデータとしてアクセスできると想定する可能性があります。これはフォンノイマンアーキテクチャ(1つのアドレススペースを持つ)には当てはまるかもしれませんが、ハーバードアーキテクチャ(コードとデータスペースを含む)には必ずしも当てはまりません。
Alexey Frunze 2013年

6
@exebook標準は初心者(特に完全なもの)向けではありません。彼らは穏やかな紹介と多数の例を提供することになっているわけではありません。彼らは正式に何かを定義しているので、専門家が正しく実装することができます。
Alexey Frunze 2013年

回答:


148

C標準は、ポインターが内部的に何であり、内部的にどのように機能するかを定義していません。これは、Cをコンパイル済みまたはインタープリター言語として実装できるプラットフォームの数を制限しないようにするためのものです。

ポインター値は、ある種のIDまたはハンドル、またはいくつかのIDの組み合わせ(たとえば、x86セグメントとオフセットにこんにちは)にすることができ、必ずしも実際のメモリーアドレスである必要はありません。このIDは、固定サイズのテキスト文字列であっても、何でもかまいません。非アドレス表現は、Cインタープリターにとって特に役立ちます。


34
説明することはあまりありません。すべての変数は、メモリにアドレスを持っています。しかし、それらへのポインタにそれらのアドレスを格納する必要はありません。代わりに、変数に1から任意の番号を付け、その番号をポインターに格納できます。実装がこれらの数値をアドレスに変換する方法、およびこれらの数値を使用してポインター演算を行う方法と、標準で必要なその他すべてのものを実装が知っている限り、それは言語標準ごとに完全に合法です。
Alexey Frunze 2013年

4
x86でそれを追加したいのですが、メモリアドレスはセグメントセレクターとオフセットで構成されているため、segment:offsetとしてポインターを表すと、まだメモリアドレスを使用しています。
2013年

6
@Lundin自分のプラットフォームとコンパイラを知っているときは、標準の一般的な性質と該当しないものを無視しても問題はありません。ただし、元の質問は一般的なものであるため、標準を無視することはできません。
Alexey Frunze 2013年

8
@Lundin革命家や科学者である必要はありません。物理的な16ビットマシンで32ビットマシンをエミュレートし、ディスクストレージを使用して64 KBのRAMを最大4 GBに拡張し、32ビットポインターをオフセットとして巨大なファイルに実装するとします。これらのポインタは実際のメモリアドレスではありません。
Alexey Frunze 2013年

6
私がこれまでに見た中で最も良い例は、Symbolics Lisp Machines(1990年頃)のC実装でした。各CオブジェクトはLisp配列として実装され、ポインターは配列とインデックスのペアとして実装されました。Lispの配列境界チェックのため、あるオブジェクトから別のオブジェクトにオーバーフローすることは決してありません。
Barmar

62

私はあなたのソースについてはわかりませんが、あなたが説明している言語のタイプはC標準から来ています:

6.5.3.2アドレス演算子と間接演算子
[...]
3. 単項&演算子は、そのオペランドのアドレスを生成します。[...]

そう...そう、ポインタはメモリアドレスを指しています。少なくともそれは、C標準がそれを意味すると示唆している方法です。

もう少し明確に言うと、ポインタはアドレスのを保持する変数です。オブジェクトのアドレス(ポインターに格納される場合があります)は、単項演算子で返されます。&

アドレス "42 Wallaby Way、Sydney"を変数に格納できます(その変数は一種の "ポインター"ですが、これはメモリアドレスではないので、 "ポインター"と適切に呼ぶものではありません)。お使いのコンピュータには、そのメモリバケットのアドレスがあります。ポインタは、住所の値を格納します(つまり、ポインタは、住所である値「42 Wallaby Way、Sydney」を格納します)。

編集:アレクセイフルンツェのコメントを拡大したいと思います。

正確にはポインタとは何ですか?C標準を見てみましょう。

6.2.5タイプ
[...]
20は、[...] のポインタ型が呼び出され、関数型またはオブジェクト型に由来することができる参照タイプ。ポインタ型は、値が参照される型のエンティティへの参照を提供するオブジェクトを表します。参照される型Tから派生したポインター型は、「Tへのポインター」と呼ばれることもあります。参照される型からのポインター型の構築は、「ポインター型の派生」と呼ばれます。ポインタ型は完全なオブジェクト型です。

基本的に、ポインターは、いくつかのオブジェクトまたは関数への参照を提供する値を格納します。やや。ポインターは、オブジェクトまたは関数への参照を提供する値を格納することを目的としていますが、常にそうであるとは限りません。

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

上記の引用は、整数をポインタに変換できることを示しています。これを行う場合(つまり、オブジェクトまたは関数への特定の参照の代わりに整数値をポインターに入れる場合)、ポインターは「参照型のエンティティを指さない場合があります」(つまり、オブジェクトまたは関数への参照)。それは私たちに何か他のものを提供するかもしれません。そして、これは、ある種のハンドルまたはIDをポインターに貼り付ける可能性がある1つの場所です(つまり、ポインターはオブジェクトを指していません。何かを表す値を格納していますが、その値はアドレスではない場合があります)。

したがって、はい、アレクセイフルンゼが言うように、ポインターがオブジェクトまたは関数へのアドレスを格納していない可能性があります。代わりに、ポインタが何らかの「ハンドル」またはIDを格納している可能性があります。これは、ポインタに任意の整数値を割り当てることによって行うことができます。このハンドルまたはIDが何を表すかは、システム/環境/コンテキストによって異なります。システム/実装がその値を理解できる限り、問題はありません(ただし、特定の値と特定のシステム/実装によって異なります)。

通常、ポインタはオブジェクトまたは関数へのアドレスを格納します。(オブジェクトまたは関数への)実際のアドレスを格納していない場合、結果は実装で定義されます(つまり、実際に何が起こり、現在ポインターが何を表しているかは、システムと実装によって異なるため、ハンドルまたはIDである可能性があります。特定のシステムですが、別のシステムで同じコード/値を使用すると、プログラムがクラッシュする可能性があります)。

それは結局思ったよりも長くなりました...


3
Cインタープリターでは、ポインターが非アドレスID /ハンドル/などを保持する場合があります。
Alexey Frunze 2013年

4
標準は、とにかくコンパイルC.に限定されるものではない@exebook
アレクセイフルンゼ

7
@ランディンブラボー!基準をもっと無視しましょう!すでにそれを十分に無視しておらず、そのためにバグが多く移植性の低いソフトウェアを生成していないかのように。また、元の質問は一般的なものなので、一般的な回答が必要であることをご了承ください。
Alexey Frunze 2013年

3
ポインターがハンドルまたはアドレス以外の何かであると他の人が言っているとき、それらは、整数をポインターにキャストすることによってポインターにデータを強制できることを意味するだけではありません。これらは、コンパイラーがポインターを実装するためにメモリー・アドレス以外のものを使用している可能性があることを意味します。DECのABIを備えたAlphaプロセッサでは、関数ポインタは関数のアドレスではなく、関数の記述子のアドレスであり、記述子には関数のアドレスと関数パラメータに関するいくつかのデータが含まれていました。ポイントは、C標準が非常に柔軟であることです。
Eric Postpischil、2013年

5
@Lundin:ポインターが実世界の既存のコンピューターシステムの100%に整数アドレスとして実装されているという主張は誤りです。コンピュータには、ワードアドレス指定とセグメントオフセットアドレス指定が存在します。コンパイラーは、nearおよびfarポインターをサポートしたまま存在します。PDP-11コンピューターが存在し、RSX-11とタスクビルダーとそのオーバーレイがあり、ポインターはディスクから関数をロードするために必要な情報を識別する必要があります。オブジェクトがメモリにない場合、ポインタはオブジェクトのメモリアドレスを持つことができません!
Eric Postpischil、2013年

39

ポインターと変数

この写真には、

pointer_pは0x12345にあるポインターで、0x34567の変数variable_vを指しています。


16
これは、ポインタとは対照的にアドレスの概念を処理しないだけでなく、アドレスが単なる整数ではないという点を完全に逃します。
Gilles「SO-悪をやめる」

19
-1、これはポインタが何であるかを説明するだけです。それはquestion--ではなかった、あなたは脇に疑問があること、すべての複雑プッシュしているあるについてを。
アレクシス、2013年

34

ポインタをアドレスと考えるのは概算です。すべての近似と同様に、時々役に立ちますが十分ではありませんが、正確ではないため、これに依存すると問題が発生します。

ポインターは、オブジェクトの検索場所を示すという点でアドレスに似ています。このアナロジーの直接的な制限の1つは、すべてのポインターが実際にアドレスを含むわけではないということです。NULLアドレスではないポインタです。ポインタ変数の内容は、実際には次の3種類のいずれかになります。

  • 間接参照できるオブジェクトのアドレスpアドレスが含まれている場合x、式の*p値はと同じですx)。
  • ヌルポインタ、そのうちのNULL一例です。
  • オブジェクトを指さない無効なコンテンツ(p有効な値を保持していない場合は、*p何でも実行でき(「未定義の動作」)、プログラムをクラッシュさせる可能性はかなり一般的です)。

さらに、ポインター(有効で非nullの場合)アドレスが含まれていると言う方がより正確です。ポインターはオブジェクトの検索場所を示しますが、それに関連する情報が多くあります。

特に、ポインタには型があります。ほとんどのプラットフォームでは、ポインターのタイプは実行時には影響しませんが、コンパイル時のタイプを超える影響があります。pintint *p;)へのポインターである場合、その後のバイトでp + 1ある整数を指しsizeof(int)ますpp + 1まだ有効なポインターであると想定しています)。qへのポインタが()charと同じアドレスを指している場合、はと同じアドレスではありません。ポインタをアドレスと考える場合、「次のアドレス」が同じ場所への異なるポインタに対して異なることはあまり直感的ではありません。pchar *q = p;q + 1p + 1

一部の環境では、メモリ内の同じ場所を指す、異なる表現(メモリ内の異なるビットパターン)を持つ複数のポインタ値が存在する可能性があります。これらは、同じアドレスを保持する異なるポインター、または同じ場所の異なるアドレスと考えることができます。この場合、メタファーは明確ではありません。==オペレータは、常に2つのオペランドは、あなたが持つことができるので、これらの環境で、同じ場所を指しているかどうかを示しますp == qにもかかわらずをpし、q異なるビットパターンを持っています。

ポインタがタイプや許可情報など、アドレス以外の他の情報を運ぶ環境さえあります。これらに遭遇することなく、プログラマーとしての人生を簡単に進むことができます。

さまざまな種類のポインターが異なる表現を持つ環境があります。表現が異なるさまざまな種類のアドレスと考えることができます。たとえば、一部のアーキテクチャには、バイトポインターとワードポインター、またはオブジェクトポインターと関数ポインターがあります。

全体として、ポインタをアドレスとして考えることは、あなたがそれを覚えている限り、それほど悪くはありません

  • アドレスであるのは有効なnull以外のポインタのみです。
  • 同じ場所に複数のアドレスを持つことができます。
  • アドレスに対して算術演算を行うことはできず、それらに順序はありません。
  • ポインタにはタイプ情報も含まれます。

逆に行くのはずっと面倒です。アドレスのように見えるすべてのものをポインタにすることはできません。ポインタのどこか深いところは、整数として読み取ることができるビットパターンとして表され、この整数はアドレスであると言えます。しかし、逆に言えば、すべての整数がポインタであるとは限りません。

最初によく知られている制限がいくつかあります。たとえば、プログラムのアドレス空間外の場所を指定する整数は、有効なポインターにはなりません。誤って整列されたアドレスは、整列が必要なデータ型に対して有効なポインタを作成しません。たとえば、int4バイトのアライメントが必要なプラットフォームでは、0x7654321を有効なint*値にすることはできません。

しかし、それはそれをはるかに超えています。なぜなら、ポインターを整数にすると、問題が発生するからです。この問題の大部分は、最適化コンパイラーがほとんどのプログラマーが期待するよりもマイクロ最適化ではるかに優れているため、プログラムの動作のメンタルモデルが深く間違っていることです。同じアドレスのポインターがあるからといって、それらが同等であるとは限りません。たとえば、次のスニペットを考えてみます。

unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);

ありふれたマシンでsizeof(int)==4、andのsizeof(short)==2場合、これは1 = 1?(リトルエンディアン)または65536 = 1?(ビッグエンディアン)のいずれかを出力することを期待するかもしれません。しかし、GCC 4.4を搭載した私の64ビットLinux PCでは:

$ c99 -O2 -Wall a.c && ./a.out 
a.c: In function main’:
a.c:6: warning: dereferencing pointer p does break strict-aliasing rules
a.c:5: note: initialized from here
0 = 1?

GCCは、この単純な例で何が問題になっているのか警告するのに十分親切です。より複雑な例では、コンパイラーが気付かない場合があります。はとはpタイプが異なるため&xpポイントを変更してもポイントは影響を受けません&x(明確に定義された例外を除きます)。したがって、コンパイラーはの値をxレジスターに保持し、*p変更時にこのレジスターを更新しない自由があります。プログラムは、同じアドレスへの2つのポインターを逆参照し、2つの異なる値を取得します!

この例の教訓は、C言語の正確な規則の範囲内であれば、(null以外の有効な)ポインターをアドレスと見なしても問題ないということです。コインの裏側は、C言語のルールは複雑であり、内部で何が起きているのかを知らない限り、直感的な感覚を得ることは難しいということです。そして、内部では、「エキゾチックな」プロセッサアーキテクチャのサポートとコンパイラの最適化の両方をサポートするために、ポインタとアドレスの結び付きがやや緩いということが起こります。

したがって、ポインタはアドレスを理解するための最初のステップと考えることができますが、その直感にあまり従わないでください。


5
+1。他の回答では、ポインタに型情報が含まれていると見落としているようです。これは、住所/ ID /その他の議論よりもはるかに重要です。
undur_gongor 2013年

+1型情報に関する優れた点。コンパイラの例が正しいかわかりません...たとえば、*p = 3pが初期化されていないときに成功することが保証されることはほとんどありません。
LarsH 2013年

@LarsHそうです、ありがとう、どうやってそれを書いたのですか?私はそれを私のPCで驚くべき動作を示す例に置き換えました。
Gilles 'SO-悪をやめる'

1
ええと、NULLは((void *)0)..?
Aniket Inge 2013

1
@ gnasher729 nullポインターポインターです。NULLそうではありませんが、ここで必要な詳細レベルの場合、これは無関係な注意散漫になります。日常のプログラミングであってNULLも、「ポインタ」を言わないものとして実装される可能性があるという事実は頻繁には現れません(主にNULL可変関数に渡されますが、それをキャストしていない場合でも) 、あなたはすでにすべてのポインタ型が同じ表現を持っていると仮定しています)。
Gilles「SO-邪悪なことをやめなさい」

19

ポインタは、アドレス自体ではなく、メモリアドレスを保持する変数です。ただし、ポインタを逆参照して、メモリ位置にアクセスできます。

例えば:

int q = 10; /*say q is at address 0x10203040*/
int *p = &q; /*means let p contain the address of q, which is 0x10203040*/
*p = 20; /*set whatever is at the address pointed by "p" as 20*/

それでおしまい。とても簡単です。

ここに画像の説明を入力してください

私が言っていることとその出力を示すプログラムはここにあります:

http://ideone.com/rcSUsb

プログラム:

#include <stdio.h>

int main(int argc, char *argv[])
{
  /* POINTER AS AN ADDRESS */
  int q = 10;
  int *p = &q;

  printf("address of q is %p\n", (void *)&q);
  printf("p contains %p\n", (void *)p);

  p = NULL;
  printf("NULL p now contains %p\n", (void *)p);
  return 0;
}

5
それはさらに混乱する可能性があります。アリス、猫が見えますか?いいえ猫の笑顔しか見えません。つまり、ポインタがアドレスである、またはポインタがアドレスを保持する変数である、またはポインタがアドレスの概念を参照する概念の名前であると言うと、本の作家はどのくらい遠くまで困惑します。
exebook 2013年

ポインタで熟練した人への@exebook、それは非常に簡単です。たぶん写真が役に立ちますか?
Aniket Inge 2013

5
ポインタは必ずしもアドレスを保持する必要はありません。Cインタープリターでは、それは何か別のID /ハンドルである可能性があります。
Alexey Frunze 2013年

「ラベル」または変数名はコンパイラ/アセンブラであり、マシンレベルでは存在しないため、メモリに表示されるべきではないと思います。
Ben

1
@Aniketポインタ変数には、ポインタ値を含めることができます。の結果をfopen変数に格納する必要があるのは、それを複数回使用する必要がある場合のみです(これは、fopenほとんどの場合)。
Gilles「SO-邪悪なことをやめなさい」

16

それらの本の著者が正確に何を意味するかを正確に伝えることは困難です。ポインターにアドレスが含まれるかどうかは、アドレスの定義方法とポインターの定義方法に依存します。

記述されているすべての回答から判断すると、一部の人々は、(1)アドレスは整数である必要があり、(2)ポインターは、仕様でそう言われていないという事実によって仮想である必要はありません。これらの前提条件があれば、ポインタには必ずしもアドレスが含まれるとは限りません。

ただし、(2)はおそらくtrueですが、(1)はおそらくtrueである必要はありません。&が@CornStalksの答えに従って演算子のアドレスと呼ばれているという事実をどうするか?これは、仕様の作成者がアドレスを含むポインタを意図していることを意味しますか?

つまり、ポインタにはアドレスが含まれていますが、アドレスは整数である必要はありませんか?多分。

これらはすべて、意味不明な意味深い話です。それは実際には全く価値がありません。ポインタの値がアドレスにならないようにコードを生成するコンパイラを思いつくだろうか?もしそうなら、何ですか?私もそう思っていました...

この本の著者(ポインタは必ずしもアドレスだけではないという最初の抜粋)がおそらく言及しているのは、ポインタに固有の型情報が付いているという事実だと思います。

例えば、

 int x;
 int* y = &x;
 char* z = &x;

yとzはどちらもポインターですが、y + 1とz + 1は異なります。それらがメモリアドレスである場合、それらの式は同じ値を与えませんか?

そしてここに、ポインタがアドレスであるかのように考えることは、通常悲しみにつながります。人々はポインタをあたかもアドレスであるかのように考えるのでバグが書かれており、これは通常悲しみにつながります。

55555はおそらくアドレスではありませんが、ポインタではありませんが、(int *)55555はポインタです。55555 + 1 = 55556、ただし(int *)55555 + 1は55559(sizeof(int)の点で+/-の差)。


1
ポインター演算を指す+1は、アドレスの演算とは異なります。
kutschkem 2013年

16ビット8086の場合、メモリアドレスはセグメントベース+オフセットで記述され、どちらも16ビットです。メモリ内で同じアドレスを与えるセグメントベース+オフセットの多くの組み合わせがあります。このfarポインタは単なる「整数」ではありません。
フォンブランド、2013年

@vonbrandなぜコメントを投稿したのかわかりません。その問題は、他の回答の下でコメントとして議論されています。他のほぼすべての答えは、address = integerであり、integer以外のものはアドレスではないと想定しています。私はこれを指摘し、それが正しい場合とそうでない場合があることに注意してください。答えの私の全体的なポイントは、それが関連していないということです。それはすべて単純なものであり、主な問題は他の回答では扱われていません。
2013年

@tang、「ポインター==アドレス」という考えは間違っています。みんなと彼らの好きな叔母が言い続けるので、それは正しくありません。
フォンブランド2013年

@vonbrand、そしてなぜ私の投稿の下でそのコメントをしたのですか?それが正しいか間違っているとは言いませんでした。実際、それは特定のシナリオ/仮定で正しいですが、常にそうであるとは限りません。投稿の要点をもう一度まとめます(2回目)。 答えの私の全体的なポイントは、それが関連していないということです。それはすべて単純なものであり、主な問題は他の回答では扱われていません。 pointer == addressまたはaddress == integerであると主張する回答にはコメントする方が適切です。セグメント:オフセットに関するアレクセイの投稿の下の私のコメントを参照してください。
タン

15

まあ、ポインターはメモリの場所を表す抽象です。引用は、ポインタをメモリアドレスであるかのように考えるのが間違っていると言っているのではなく、単に「通常は悲しみにつながる」とだけ言っていることに注意してください。言い換えれば、それはあなたが誤った期待を持つことにつながります。

悲しみの最もありそうな原因は確かにポインタ演算でありこれは実際にはCの強みの1つです。ポインターがアドレスである場合、ポインター演算はアドレス演算であると予想されます。しかし、そうではありません。たとえば、アドレスに10を追加すると、アドレス単位が10だけ大きいアドレスが得られます。ただし、ポインタに10を追加すると、ポインタが指すオブジェクトの種類のサイズの10倍に増加します(実際のサイズではなく、アライメント境界に切り上げられます)。でint *32ビット整数を持つ通常のアーキテクチャ上、これに10を加えて40アドレッシングユニット(バイト)によってそれをインクリメントであろう。経験豊富なCプログラマーはこれを認識しており、それと共存していますが、作者は明らかにずさんなメタファーのファンではありません。

ポインタの内容がメモリの場所どのように表すという追加の質問があります。多くの回答で説明されているように、アドレスは常にint(またはlong)とは限りません。一部のアーキテクチャでは、アドレスは「セグメント」にオフセットを加えたものです。ポインターには、現在のセグメントへのオフセット(「近くの」ポインター)だけが含まれている場合もありますが、それ自体は一意のメモリアドレスではありません。また、ハードウェアが認識しているように、ポインタの内容はメモリアドレスと間接的な関係しか持たない場合があります。しかし、引用された引用の作者は表現についてさえ言及していないので、彼らが考えていたのは表現ではなく概念の同等性だったと思います。


12

これは、過去に混乱した人々に説明した方法です。ポインタには、その動作に影響を与える2つの属性があります。それは持っていた値(一般的な環境では)あり、メモリアドレス、およびタイプあなたにそれがで指しているオブジェクトの種類とサイズを伝えます。

たとえば、次の場合:

union {
    int i;
    char c;
} u;

この同じオブジェクトを指す3つの異なるポインタを使用できます。

void *v = &u;
int *i = &u.i;
char *c = &u.c;

これらのポインターの値を比較すると、それらはすべて同じです。

v==i && i==c

ただし、各ポインターをインクリメントすると、それらが指すが関連するようになります。

i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c

変数icするので、この時点では異なる値を持つことになりますi++原因は、i次のアクセス可能な整数のアドレスを含むように、とc++なりc次のアドレス指定可能な文字を指すように。通常、整数は文字よりも多くのメモリを消費するため、両方がインクリメントされた後iよりも大きな値になりcます。


2
+1ありがとうございます。ポインターを使用すると、人間の体を彼の魂から切り離すことができるのと同じように、値と型は切り離せません。
Aki Suihkonen

i == cの形式が正しくありません(異なる型へのポインターを比較できるのは、一方から他方への暗黙の変換がある場合のみです)さらに、キャストでこれを修正すると、変換が適用されたことになり、変換によって値が変更されるかどうかについては議論の余地があります。(そうではないと主張することもできますが、それはこの例で証明しようとしていたのと同じことを主張しているだけです)。
MM

8

マーク・ベッシーはすでにそれを言ったが、理解されるまでこれは再び強調される必要がある。

ポインタは、リテラル3よりも変数と関係があります。

ポインター、(アドレスの)値と型(読み取り専用などの追加のプロパティを含む)のタプルです。タイプ(および存在する場合は追加のパラメーター)は、コンテキストをさらに定義または制限できます。例えば。__far ptr, __near ptr:アドレスのコンテキストは何ですか:スタック、ヒープ、線形アドレス、どこかからのオフセット、物理メモリなど。

ポインタ演算を整数演算と少し異なるのは、typeのプロパティです。

変数ではないというポインターの反例は多すぎて無視できません

  • fopenはFILEポインターを返します。(変数はどこですか)

  • スタックポインターまたはフレームポインターは通常アドレス指定できないレジスター

    *(int *)0x1231330 = 13; -任意の整数値をpointer_of_integer型にキャストし、変数を導入せずに整数を読み書きする

Cプログラムの存続期間中には、アドレスを持たない一時的なポインターのインスタンスが他にも多数あります。したがって、それらは変数ではなく、コンパイル時に関連する型を持つ式/値です。


8

あなたは正しく、正気です。通常、ポインターは単なるアドレスであるため、整数にキャストして任意の演算を実行できます。

しかし、ポインタはアドレスの一部に過ぎない場合があります。一部のアーキテクチャでは、ポインタがベースを追加してアドレスに変換されるか、別のCPUレジスタが使用されます。

しかし最近では、フラットメモリモデルとC言語がネイティブにコンパイルされたPCとARMアーキテクチャでは、ポインタは1次元のアドレス指定可能なRAM内のある場所への整数アドレスであると考えても問題ありません。


PC ...フラットメモリモデル?セレクターとは
2013年

正しい。そして、おそらく別のコードとデータスペースで次のアーキテクチャの変更が発生した場合、または誰かが由緒あるセグメントアーキテクチャに戻った場合(これはセキュリティにとって非常に意味があります)、セグメント番号+オフセットにキーを追加して権限を確認することさえできます)素敵な「ポインターは単なる整数」がクラッシュします。
フォンブランド、2013年

7

ポインターは、Cの他の変数と同様に、基本的に1つ以上の連結されたunsigned char値で表されるビットのコレクションです(他の種類のカレイブルと同様に、値sizeof(some_variable)の数をunsigned char示します)。ポインターが他の変数と異なる点は、Cコンパイラーがポインターのビットを、変数が格納されている可能性のある場所を何らかの方法で識別するものとして解釈することです。Cでは、他の一部の言語とは異なり、複数の変数にスペースを要求し、そのセット内の任意の値へのポインターを、そのセット内の他の変数へのポインターに変換することができます。

多くのコンパイラーは、実際のマシンアドレスを格納するビットを使用してポインターを実装しますが、可能な実装はそれだけではありません。実装は、プログラムが使用していたすべてのメモリオブジェクト(変数のセット)のハードウェアアドレスと割り当てられたサイズをリストする1つの配列(ユーザーコードにアクセスできない)を保持し、各ポインターに配列へのインデックスを含めることができますそのインデックスからのオフセット。そのような設計により、システムはコードが所有するメモリでのみ動作するようにコードを制限できるだけでなく、あるハードウェア項目へのポインタが別のメモリ項目へのポインタに誤って変換されないようにします(ハードウェアを使用するシステム)。アドレス、fooまたは場合barメモリに連続して格納されている10項目の配列の、「の11番目の」項目へのポインタfoo代わりにの最初の項目をbarただし、各「ポインター」がオブジェクトIDとオフセットであるシステムでは、コードfooが割り当てられた範囲を超えてポインターをインデックス付けしようとすると、システムがトラップする可能性があります。また、ポインタに関連付けられている物理アドレスを移動できるため、このようなシステムでメモリの断片化の問題を解消することもできます。

ポインタは多少抽象的ですが、完全に標準に準拠したCコンパイラがガベージコレクタを実装できるほど抽象的ではないことに注意してください。Cコンパイラは、ポインタを含むすべての変数がunsigned char値のシーケンスとして表されることを指定しています。任意の変数を指定すると、それを一連の数値に分解し、後でその一連の数値を元の型の変数に戻すことができます。その結果、プログラムがcallocストレージへのポインターを受け取り、そこに何かを格納し、ポインターを一連のバイトに分解し、それらを画面に表示して、それらへのすべての参照を消去します。プログラムがキーボードからいくつかの数値を受け入れ、それらをポインタに再構成してから、そのポインタからデータを読み取ろうとした場合、ユーザーがプログラムが以前に表示していたものと同じ数値を入力した場合、プログラムはデータを出力する必要がありますそれは格納されていましたcallocのメモリ。コンピュータがユーザが表示された数字のコピーを作成したかどうかを知ることができる考えられる方法はないので、コンピュータが前述のメモリが将来アクセスされるかどうかを知ることができるとは考えられない。


大きなオーバーヘッドで、おそらく数値を「漏らす」可能性のあるポインター値の使用を検出し、ガベージコレクターがそれを収集または再配置しないように割り当てを固定できます(freeもちろん、明示的に呼び出されない限り)。結果の実装がそれほど有用かどうかは別の問題です。収集する能力が制限されすぎる可能性があるためですが、少なくともガベージコレクターと呼ぶことができます:-)ポインターの割り当てと演算は値を「漏らさない」が、char*不明な出所へのアクセスはすべてチェックする必要があります。
Steve Jessop

@SteveJessop:コードがどのポインターを解放する必要があるかを知ることが不可能であるため、そのような設計は役に立たないよりも悪いと思います。ポインターのように見えるものを1つであると想定するガベージコレクターは、保守的すぎる可能性がありますが、一般に、ポインターのように見えるがそうではないものは変更される可能性があるため、「永続的な」メモリーリークを回避できます。ポインタをバイトに分解しているように見えるアクションを実行すると、ポインタが永久にフリーズすることは、メモリリークの確実なレシピです。
スーパーキャット2015年

パフォーマンス上の理由でとにかく失敗すると思います-すべてのアクセスがチェックされるのでコードをゆっくり実行したい場合は、Cで記述しないでください;-)私はあなたよりもCプログラマの創意工夫に高い期待を寄せています。不便ではありますが、不必要に割り当てを固定することを避けるのはおそらく信じられないことではないと思います。とにかく、C ++はこの問題に対処するために「安全に派生したポインター」を正確に定義しているので、Cポインターの抽象性を合理的に有効なガベージコレクションをサポートするレベルまで高めたい場合の対処方法がわかります。
Steve Jessop、2015年

@SteveJessop:GCシステムが役立つためには、free呼び出されていないメモリを確実に解放できるか、解放されたオブジェクトへの参照がライブオブジェクトへの参照にならないようにする必要があります[必要なリソースを使用している場合でも明示的なライフタイム管理、GCは後者の機能を引き続き有効に実行できます]。Nが大きくなると、N個のオブジェクトが同時に不必要に固定される確率がゼロに近づくと、オブジェクトをそれらへのライブ参照があると誤って見なすGCシステムが使用できる場合があります。コンパイラエラーを
報告

...有効なC ++であるが、コンパイラーがポインターが認識できない形式に変換されないことをコンパイラーが証明できないコードの場合、実際には絶対にプログラムを実行しないというリスクをどのように回避できるかわかりません。整数はそうすることであると誤って見なされる可能性があるため、ポインタを使用します。
スーパーキャット2015年

6

ポインターは、C / C ++でネイティブに使用可能な変数型であり、メモリーアドレスが含まれています。他の変数と同様に、独自のアドレスを持ち、メモリを消費します(量はプラットフォーム固有です)。

混乱の結果として目にする1つの問題は、ポインタを値で渡すだけで、関数内の指示対象を変更しようとすることです。これにより、関数のスコープにポインターのコピーが作成され、この新しいポインターが「ポイント」する場所に変更を加えても、関数を呼び出したスコープのポインターの参照先は変更されません。関数内の実際のポインターを変更するには、通常、ポインターをポインターに渡します。


1
通常、これはハンドル/ IDです。通常、これは単純なアドレスです。
Alexey Frunze 2013年

私はウィキペディアのハンドルの定義に少しPCが合うように答えを調整しました。ハンドルは単にポインタへの参照である場合があるため、ポインタをハンドルの特定のインスタンスとして参照するのが好きです。
マシューサンダース2013年

6

簡単な要約 (これも上に置きます):

(0)ポインターをアドレスとして考えることは、多くの場合、優れた学習ツールであり、通常のデータ型へのポインターの実際の実装です。

(1)しかし、多くの場合、おそらくほとんどの場合、コンパイラへの関数へのポインタはアドレスではなく、アドレス(通常は2倍、場合によってはそれ以上)よりも大きいか、実際には関数のアドレスや以下のものなどを含むメモリ内の構造体へのポインタです。一定のプール。

(2)データメンバーへのポインターとメソッドへのポインターは、しばしば見知らぬものになります。

(3)FARおよびNEARポインターの問題があるレガシーx86コード

(4)安全な「ファットポインタ」を使用したいくつかの例、特にIBM AS / 400。

もっと見つけられると思います。

詳細:

UMMPPHHH !!!!! これまでの回答の多くは、かなり典型的な「プログラマウィニー」の回答ですが、コンパイラウィニーやハードウェアウィニーではありません。私はハードウェアweenieのふりをして、しばしばコンパイラweeniesで作業するので、2セント投入します。

多くの、おそらくほとんどのCコンパイラでTは、型のデータへのポインタは、実際にはのアドレスですT

いいね。

しかし、これらのコンパイラの多くでさえ、特定のポインタはアドレスではありません。これを見るとわかりsizeof(ThePointer)ます。

たとえば、関数へのポインタは、通常のアドレスよりもかなり大きい場合があります。または、間接参照のレベルが含まれる場合があります。 この記事Intel Itaniumプロセッサを含む1つの説明を提供しますが、他の説明も見ました。通常、関数を呼び出すには、関数コードのアドレスだけでなく、関数の定数プールのアドレス(コンパイラが生成する必要があるのではなく、定数が単一のロード命令でロードされるメモリ領域)も知っている必要があります。いくつかのLoad Immediate、Shift、およびOR命令からの64ビット定数。したがって、単一の64ビットアドレスではなく、2つの64ビットアドレスが必要です。一部のABI(アプリケーションバイナリインターフェイス)はこれを128ビットとして移動しますが、他のレベルは間接参照のレベルを使用し、関数ポインターは実際には前述の2つの実際のアドレスを含む関数記述子のアドレスです。どちらが良いですか?あなたの視点に依存します:パフォーマンス、コードサイズ、いくつかの互換性の問題-多くの場合、コードはポインターをlongまたはlong longにキャストできると想定していますが、long longが正確に64ビットであると想定している場合もあります。そのようなコードは標準に準拠していない場合がありますが、それでも顧客はそれを機能させたい場合があります。

私たちの多くは、NEAR POINTERとFAR POINTERSを備えた、古いIntel x86セグメントアーキテクチャの苦しい思い出を持っています。ありがたいことに、これらは今ではほとんど絶滅しているので、簡単な要約のみ:16ビットのリアルモードでは、実際の線形アドレスは

LinearAddress = SegmentRegister[SegNum].base << 4 + Offset

プロテクトモードでは、

LinearAddress = SegmentRegister[SegNum].base + offset

結果のアドレスは、セグメントに設定された制限に対してチェックされます。一部のプログラムは実際には標準のC / C ++ FARおよびNEARポインター宣言を使用しませんでしたが、多くの人が言っただけです*T---しかし、コンパイラーおよびリンカースイッチがあったため、たとえば、コードポインターはポインターの近くにある可能性があり、 CS(コードセグメント)レジスタ。データポインターはFARポインターの場合があり、16ビットセグメント番号と48ビット値の32ビットオフセットの両方を指定します。さて、これらの両方の量は確かに住所に関連していますが、同じサイズではないため、どちらが住所ですか?さらに、セグメントには、実際のアドレスに関連するものに加えて、読み取り専用、読み取り/書き込み、実行可能などの権限も含まれていました。

より興味深い例であるIMHOは、IBM AS / 400ファミリーです(またはおそらくそうでした)。このコンピューターは、C ++でOSを実装した最初のコンピューターの1つです。このmachimeのポインターは通常、実際のアドレスサイズの2倍でした(例:このプレゼンテーションなど)。128ビットのポインタですが、実際のアドレスは48-64ビットでした、そしてまた、いくつかの追加情報、いわゆる機能と呼ばれ、読み取り、書き込み、およびバッファオーバーフローを防ぐための制限などのアクセス許可を提供しました。はい:これはC / C ++と互換性があります-これがユビキタスである場合、中国のPLAとスラブのマフィアはそれほど多くの西側のコンピューターシステムに侵入しません。しかし、歴史的に、ほとんどのC / C ++プログラミングはパフォーマンスのセキュリティを無視してきました。最も興味深いことに、AS400ファミリは、オペレーティングシステムが安全なポインタを作成することを許可しました。これは、非特権コードに与えることができますが、非特権コードは偽造または改ざんできませんでした。繰り返しになりますが、セキュリティは標準に準拠していますが、標準に準拠していないずさんなC / C ++コードの多くは、このような安全なシステムでは機能しません。ここでも、公式の基準があります。

次に、セキュリティsoapboxから降りて、(さまざまなタイプの)ポインタが実際にアドレスされないことが多い他のいくつかの方法について説明します。データメンバーへのポインター、メンバー関数メソッドへのポインター、およびそれらの静的バージョンは、通常の住所。このポストは 言います:

これを解決するには多くの方法があります[単一の継承と複数の継承、および仮想継承に関連する問題]。Visual Studioコンパイラがそれを処理する方法を次に示します。多重継承されたクラスのメンバー関数へのポインターは実際には構造です。そして、「関数ポインターをキャストするとサイズが変わる可能性があります!」と言い続けます。

(おそらく)セキュリティについての私の理解から推測できると思いますが、私はポインタが生のアドレスよりも機能のように扱われるC / C ++ハードウェア/ソフトウェアプロジェクトに携わってきました。

私は続けることができますが、あなたがアイデアを得ることを望みます。

簡単な要約 (これも上に置きます):

(0)ポインターをアドレスとして考えることは、多くの場合、優れた学習ツールであり、通常のデータ型へのポインターの実際の実装です。

(1)しかし、多くの場合、おそらくほとんどの場合、コンパイラへの関数へのポインタはアドレスではなく、アドレスよりも大きく(通常は2倍、時にはそれ以上)、実際にはメモリ内の構造体へのポインタであり、関数のアドレスや以下のようなものを含みます一定のプール。

(2)データメンバーへのポインターとメソッドへのポインターは、しばしば見知らぬものになります。

(3)FARおよびNEARポインターの問題があるレガシーx86コード

(4)安全な「ファットポインタ」を使用したいくつかの例、特にIBM AS / 400。

もっと見つけられると思います。


16ビットのリアルモードLinearAddress = SegmentRegister.Selector * 16 + Offset(16シフトではなく16倍に注意)。プロテクトモードLinearAddress = SegmentRegister.base + offset(種類の乗算なし。セグメントベースはGDT / LDTに格納され、そのままセグメントレジスタにキャッシュされます)。
Alexey Frunze 2013年

また、セグメントベースについても正しいです。覚えていなかった。これは、オプションで4K倍になるセグメント制限です。セグメントベースは、セグメント記述子をメモリからセグメントレジスタにロードするときに、ハードウェアによってスクランブルを解除する必要があるだけです。
Krazy Glew 2013年

4

ポインタは、メモリ位置のアドレス(通常、別の変数のメモリアドレス)を保持するために使用される別の変数です。


それで、指示先は実際にはメモリアドレスですか?著者に同意しませんか?ただ理解しようとしています。
d0rmLife 2013年

ポインターの主な機能は、何かを指すことです。それがどの程度正確に達成され、実際のアドレスがあるかどうかは定義されていません。ポインタは、実際のアドレスではなく、単なるID /ハンドルである可能性があります。
Alexey Frunze 2013年

4

このように見ることができます。ポインタは、アドレス指定可能なメモリ空間のアドレスを表す値です。


2
ポインターは、必ずしも実メモリー・アドレスをその中に保持する必要はありません。私の回答とその下のコメントを参照してください。
Alexey Frunze 2013年

what ....スタックの最初の変数へのポインタは0を出力しません。実装方法に応じて、スタックフレームの上部(または下部)を出力します。
2013年

@thang最初の変数の上部と下部は同じです。そして、このスタックの場合、トップまたはボトムのアドレスは何ですか?
Valentin Radu 2013年

@ValentinRadu、それを試してみませんか?明らかにそれを試していないのは明らかです。
2013年

2
@thangあなたの言う通り、私はいくつかの本当に悪い仮定をしました、私の防御のためにここでは午前5時です。
Valentin Radu 2013年

3

ポインタは、通常は別の変数のメモリアドレスを含むことができる別の変数です。ポインタは変数であり、メモリアドレスも持っています。


1
必ずしも住所ではありません。ところで、回答を投稿する前に、既存の回答やコメントを読みましたか?
Alexey Frunze 2013年

3

Cポインタはメモリアドレスと非常に似ていますが、マシン依存の詳細が抽象化されており、下位レベルの命令セットにはない機能もあります。

たとえば、Cポインターは比較的豊富に型指定されています。構造体の配列を介してポインターをインクリメントすると、ある構造体から別の構造体にうまくジャンプします。

ポインターは変換ルールに従い、コンパイル時の型チェックを提供します。

ソースコードレベルで移植可能な特別な「nullポインタ」値がありますが、その表現は異なる場合があります。値がゼロの整数定数をポインターに割り当てると、そのポインターはNULLポインター値をとります。その方法でポインタを初期化した場合も同様です。

ポインタはブール変数として使用できます。null以外の場合はtrue、nullの場合はfalseをテストします。

機械語では、ヌルポインターが0xFFFFFFFFのような面白いアドレスである場合、その値を明示的にテストする必要がある場合があります。Cはそれをあなたから隠します。nullポインターが0xFFFFFFFFの場合でも、を使用してテストできますif (ptr != 0) { /* not null! */}

型システムを破壊するポインタを使用すると、未定義の動作が発生しますが、機械語の同様のコードは十分に定義されている可能性があります。アセンブラは、作成した命令をアセンブルしますが、Cコンパイラは、ユーザーが何か間違ったことをしていないという前提に基づいて最適化します。もしfloat *pへのポインタがlong n変数、および*p = 0.0実行され、コンパイラはこれを処理するために必要とされていません。以降のの使用でnは、float値のビットパターンを読み取る必要はありませんが、おそらく、n触れられていない「厳密なエイリアシング」の仮定に基づいた最適化されたアクセスになります。つまり、プログラムは適切に動作しているため、をp指し示すべきではありませんn

Cでは、コードへのポインターとデータへのポインターは異なりますが、多くのアーキテクチャーではアドレスは同じです。ターゲットアーキテクチャにない場合でも、「ファット」ポインタを持つCコンパイラを開発できます。ファットポインターとは、ポインターが単なるマシンアドレスではなく、境界チェックのために指し示されているオブジェクトのサイズに関する情報などの他の情報を含んでいることを意味します。移植可能なプログラムは、そのようなコンパイラに簡単に移植できます。

ご覧のとおり、マシンアドレスとCポインタの間には多くの意味上の違いがあります。


NULLポインタは、すべてのプラットフォームでの動作とは異なります。上記のCiscoIPPhoneに対する私の返信を参照してください。NULL == 0は、x86ベースのプラットフォームでのみ適用される前提です。コンベンションでは、新しいプラットフォームはx86と一致する必要があると述べていますが、特に組み込みの世界ではそうではありません。!編集:また、Cは、ハードウェアからポインタ道の抽象値に何もしません- NULL = 0のプラットフォーム上のNULLテストとして「!PTR = 0」になるではない仕事
DX-MON

1
DX-MON、これは標準のCでは完全に間違っています。NULLは0であると考えられており、ステートメント内で相互に使用できます。ハードウェアでのNULLポインター表現がすべて0ビットであるかどうかは、ソースコードでの表現方法とは無関係です。
Mark Bessey 2013年

@ DX-MON正しい事実を扱っていないようです。Cでは、整数定数式は、NULLポインターがNULLアドレスであるかどうかに関係なく、NULLポインター定数として機能します。ptr != 0nullテストではないCコンパイラを知っている場合は、そのアイデンティティを明らかにしてください(ただし、その前に、ベンダーにバグレポートを送信してください)。
Kaz

私はあなたが何をしているのかわかりますが、ポインターとメモリアドレスを混同しているため、nullポインターに関するコメントは一貫していません-質問で引用されている引用が避けるように忠告しているとおりです!正しい説明:Cは、オフセット0のメモリアドレスが有効かどうかに関係なく、ヌルポインターを0と定義します。
アレクシス2013年

1
@alexis章と節をお願いします。Cは、ヌルポインターがゼロであることを定義していません。Cでは、ゼロ(または値がゼロの整数定数式)を、nullポインター定数を表す構文として定義していますfaqs.org/faqs/C-faq/faq(セクション5)。
Kaz

3

ポインタを理解する前に、オブジェクトを理解する必要があります。オブジェクトは存在し、アドレスと呼ばれる位置指定子を持つエンティティです。ポインタは、他の変数とC同様に、その型が呼び出されpointer、その内容が次の操作をサポートするオブジェクトのアドレスとして解釈される変数です。

+ : A variable of type integer (usually called offset) can be added to yield a new pointer
- : A variable of type integer (usually called offset) can be subtracted to yield a new pointer
  : A variable of type pointer can be subtracted to yield an integer (usually called offset)
* : De-referencing. Retrieve the value of the variable (called address) and map to the object the address refers to.
++: It's just `+= 1`
--: It's just `-= 1`

ポインターは、現在参照しているオブジェクトのタイプに基づいて分類されます。重要な情報は、オブジェクトのサイズだけです。

すべてのオブジェクトは、オブジェクト&の位置指定子(アドレス)をポインターオブジェクトタイプとして取得する操作(アドレス)をサポートします。これは&、結果の型がオブジェクト型のポインターであるポインターではなく、オブジェクトの操作として呼び出すのが理にかなっているため、命名法を取り巻く混乱を和らげるはずです。

この説明では、メモリの概念は省略しています。


一般的なシステムにおける一般的なポインタの抽象的な現実についてのあなたの説明が好きです。しかし、おそらく記憶について議論することは役立つでしょう。実は、自分で言えば、それは...!接続を議論することは、全体像を理解するのに非常に役立つと思います。とにかく+1 :)
d0rmLife

@ d0rmLife:全体像をカバーする他の回答で十分な説明があります。別の見方として、数学的な抽象的な説明をしたかっただけです。また、私見ですが、「それ&自体のポインタではなくオブジェクトに結び付けられている」ため、「アドレス」として呼び出す際の混乱が少なくなります
Abhijit

問題はありませんが、私は十分な説明が何であるかを自分で決めます。データ構造とメモリ割り当てを完全に説明するには、1冊のテキストでは不十分です。;)....とにかく、あなたの答えは、たとえそれが目新しいものでなくても、まだ役に立ちます
d0rmLife 2013年

メモリの概念がなければ、ポインタを処理しても意味がありません。オブジェクトがメモリなしで存在する場合、それはアドレスのない場所になければなりません-例えばレジスターの中に。「&」を使用できるようにするには、メモリが前提となります。
Aki Suihkonen

3

アドレスは、通常はバイトごとに、固定サイズのストレージを整数として識別するために使用されます。これは、バイトアドレスと正確に呼ばれ、ISO Cでも使用されます。たとえば、ビットごとにアドレスを作成する方法は他にもいくつかあります。ただし、バイトアドレスのみが頻繁に使用されるため、通常は「バイト」を省略します。

技術的には、(ISO)Cの「値」という用語の定義は次のとおりであるため、アドレスがCの値になることはありません。

特定のタイプを持つと解釈された場合のオブジェクトの内容の正確な意味

(私が強調しました。)しかし、Cにはそのような「アドレスタイプ」はありません。

ポインターは同じではありません。ポインタがの一種であるタイプ C言語インチ いくつかの異なるポインタ型があります。それらは必ずしも言語の同一のルールのセットに従う必要はありません。例えば、++int*vsの値に対する影響char*

Cの値は、ポインター型にすることができます。これはポインタ値と呼ばれます。明確にするために、ポインター値はC言語のポインターではありません。しかし、Cではあいまいになる可能性が低いため、これらを一緒に使用することに慣れています。式pを「ポインター」として呼び出す場合、それは単なるポインター値であり、型ではありません。Cの名前付き型はで表現されますが、type-nameまたはtypedef-nameで表現されます

他のいくつかは微妙です。Cユーザーとして、まず、次のことを理解objectする必要があります。

実行環境のデータストレージの領域。その内容は値を表すことができます

オブジェクトは、特定のタイプの値を表すエンティティです。ポインタはオブジェクト型です。したがって、を宣言するとint* p;p「ポインタ型のオブジェクト」または「ポインタオブジェクト」を意味します。

標準によって標準的に定義された「変数」はないことに注意してください(実際、標準テキストではISO Cによって名詞として使用されることはありません)。ただし、非公式には、他の言語のようにオブジェクトを変数と呼びます。(しかし、それほど正確ではありません。たとえば、C ++では、変数はオブジェクトではない参照型の規範的である可能性があります。)「ポインターオブジェクト」または「ポインター変数」という句は、上記の「ポインター値」のように扱われることがあります。おそらくわずかな違い。(もう1つの例は「配列」です。)

ポインタは型であり、アドレスはCでは実質的に「型なし」であるため、ポインタ値はおおよそアドレスを「含む」。そして、ポインタ型の式はアドレスを生成することができます、例えば

ISO C11 6.5.2.3

3単項&演算子は、そのオペランドのアドレスを生成します。

この表現はWG14 / N1256、つまりISO C99:TC3によって導入されていることに注意してください。C99には

3単項&演算子は、そのオペランドのアドレスを返します。

これは委員会の意見を反映しています。アドレスは単項演算子によって返されるポインター値ではありません&

上記の言葉遣いにもかかわらず、標準でさえまだいくつかの混乱があります。

ISO C11 6.6

9 アドレス定数は、nullポインター、静的ストレージ期間のオブジェクトを指定する左辺値へのポインター、または関数指定子へのポインター

ISO C ++ 11 5.19

3 ... アドレス定数式は、静的ストレージ期間を持つオブジェクトのアドレス、関数のアドレス、またはnullポインター値、またはprvalueコア定数式に評価されるポインター型のprvalueコア定数式です。タイプのstd::nullptr_t。...

(最近のC ++標準ドラフトでは別の表現を使用しているため、この問題はありません。)

実際には、Cの「アドレス定数」とC ++の「アドレス定数式」は、どちらもポインタ型(または少なくともC ++ 11以降の「ポインタのような」型)の定数式です。

組み込みの単項&演算子は、CおよびC ++では「アドレス」として呼び出されます。同様にstd::addressof、C ++ 11で導入されました。

これらの命名は誤解を招く可能性があります。のではなく、/結果が含まれているアドレスが得られます。結果の式は、彼らがあると解釈することと思いますので、ポインタ型であるであるアドレス。


2

「それはアドレスが何であるかを知らない人を混乱させるから」と書いてあります-また、それは本当です:あなたがアドレスが何であるかを学べば、あなたは混乱しないでしょう。理論的には、ポインタは別のものを指す変数であり、実際にはアドレスを保持しています。これは、それが指す変数のアドレスです。なぜこの事実を隠す必要があるのか​​はわかりませんが、ロケット科学ではありません。ポインタを理解すれば、コンピュータがどのように機能するかを理解するために一歩近づくでしょう。どうぞ!


2

考えてみると、セマンティクスの問題だと思います。他の人がすでにここで言及しているように、C標準はポインタを参照オブジェクトへのアドレスを保持するものとして参照するため、著者は正しいとは思いません。ただし、address!=メモリアドレス。アドレスは実際にはC標準に従って何でもかまいませんが、最終的にはメモリアドレスにつながります。ポインタ自体は、ID、オフセット+セレクタ(x86)、メモリを(マッピング後に)記述できる限り、実際には何でもかまいません。アドレス可能な空間のアドレス。


ポインタアドレスを保持します(nullの場合は保持しません)。しかし、それアドレスであることとはかけ離れいます。たとえば、同じアドレスへの2つのポインタが、タイプが異なる場合は、多くの状況で同等ではありません。
Gilles 'SO-悪をやめる'

@Gilles- int i=5> i 5のように "being"が表示される場合、ポインタはyesです。また、nullにもアドレスがあります。通常は無効な書き込みアドレス(必ずしもそうとは限りませんが、x86リアルモードを参照してください)ですが、それでもアドレスは無効です。実際には、nullの要件は2つだけです。実際のオブジェクトへのポインターと等しくないことが比較され、2つのnullポインターは等しいと比較されます。
Valentin Radu 2013年

逆に、nullポインタは、オブジェクトのアドレスと等しくないことが保証されています。nullポインターの逆参照は未定義の動作です。「ポインタがアドレスである」と言うことの大きな問題は、それらが異なる動作をすることです。場合はp、ポインタで、p+1常に1ずつインクリメントアドレスではありません
ジル「SO-停止されて悪」

コメントをもう一度読んでくださいit's guaranteed to compare unequal to a pointer to an actual object。ポインタの計算については、要点はわかりません。「+」操作で必ずしも1バイトが追加されなくても、ポインタの値はアドレスのままです。
Valentin Radu

1

CまたはC ++のポインターが単純なメモリアドレスと異なるもう1つの方法は、他の回答では見られなかったさまざまなポインターの種類によるものです(合計サイズを考えると、見落としている可能性があります)。しかし、これはおそらく最も重要なものです。経験豊富なC / C ++プログラマーでも、つまずく可能性があるからです。

コンパイラーは、互換性のない型のポインターが明確に指す場合でも同じアドレスを指していないと想定する場合があります。これにより、単純なpointer == addressモデルでは不可能な動作が発生する可能性があります。次のコードを考えます(を想定sizeof(int) = 2*sizeof(short))。

unsigned int i = 0;
unsigned short* p = (unsigned short*)&i;
p[0]=p[1]=1;

if (i == 2 + (unsigned short)(-1))
{
  // you'd expect this to execute, but it need not
}

if (i == 0)
{
  // you'd expect this not to execute, but it actually may do so
}

には例外があるためchar*、値を操作することchar*は可能です(ただし、移植性は高くありません)。


0

簡単なまとめ:ACアドレスは、特定のタイプの値であり、通常はマシンレベルのメモリアドレスとして表されます。

修飾されていない単語「ポインター」はあいまいです。Cには、ポインターオブジェクト(変数)、ポインター、ポインター、およびポインター値があります

「ポインター」という単語を「ポインターオブジェクト」を意味するために使用することは非常に一般的であり、それによって混乱が生じる可能性があります。そのため、「ポインター」を名詞ではなく形容詞として使用しようとしています。

C標準では、少なくとも一部のケースでは、「ポインター値」を意味するために「ポインター」という単語を使用しています。たとえば、mallocの説明では、「nullポインターまたは割り当てられた空間へのポインターを返す」と記述されています。

では、Cのアドレスとは何でしょうか。これはポインタ値、つまり特定のポインタ型の値です。(ただし、nullポインター値は、何のアドレスでもないため、必ずしも「アドレス」と呼ばれるわけではありません)。

規格の単項演算&子の説明では、「そのオペランドのアドレスを生成する」と記載されています。C標準の範囲外では、「アドレス」という単語は一般に(物理または仮想)メモリアドレスを指すために使用され、通常は1ワードのサイズです(「システム」上の「ワード」が何であっても)。

Cの「アドレス」は通常、マシンのアドレスとして実装されます。Cのint値が通常、マシンのワードとして実装されるのと同じです。ただし、Cアドレス(ポインター値)は単なるマシンアドレスではありません。これは通常、マシンアドレスとして表される値であり、特定のタイプを持つ値です。


0

ポインタ値アドレスです。ポインタ変数、アドレスを格納できるオブジェクトです。これは、標準がポインタを定義するものだからです。Cの初心者は、ポインターとそれが指すものの違いがよく分からないため、Cの初心者にそれを伝えることが重要です(つまり、エンベロープと建物の違いがわかりません)。アドレスの概念(すべてのオブジェクトにアドレスがあり、それがポインタに格納されるものです)は、それを区別するために重要です。

ただし、標準は特定の抽象化レベルで話し合います。「アドレスが何であるかを知っている」が、Cを初めて使用する人について著者が話している人々は、おそらくアセンブリ言語をプログラミングすることによって、異なるレベルの抽象化でアドレスについて必然的に学んだに違いありません。Cの実装が、CPUのオペコードが使用するのと同じ表現(この節で​​は「ストアアドレス」と呼ばれる)を使用しているという保証はありません。

彼はさらに「完全に合理的なアドレス操作」について話します。C標準に関する限り、基本的に「完全に合理的なアドレス操作」などはありません。加算はポインタで定義され、それは基本的にそれです。確かに、ポインタを整数に変換し、ビット単位または算術演算を実行してから、元に戻すことができます。これは標準での動作が保証されていないため、そのコードを記述する前に、特定のC実装がどのようにポインターを表し、その変換を実行するかを理解する必要があります。それはおそらくあなたが期待するアドレス表現を使用しますが、マニュアルを読んでいないのでそれはあなたのせいではありません。それは混乱ではありません、それは間違ったプログラミング手順です;-)

要するに、Cは著者よりも抽象的なアドレスの概念を使用しています。

もちろん、著者の演説という概念も、問題の最低レベルの言葉ではありません。複数のチップにまたがる仮想メモリマップと物理RAMアドレッシングでは、CPUがアクセスしたい「ストアアドレス」であることを伝える数値は、基本的に、必要なデータが実際にハードウェアに配置されている場所とは関係ありません。それは間接と表現のすべての層ですが、作者は特権を与えるものを1つ選択しました。Cについて話すときにそうする場合は、Cレベルの特権を選択してください。

個人的には、Cをアセンブリプログラマーに紹介する場合を除いて、作者の発言がそれほど役に立たないと思います。ポインタの値がアドレスではないと言うことは、より高水準の言語から来る人々にとって確かに役に立ちません。アドレスが何であるかをCPUが独占しているため、Cポインタの値が「アドレスではない」と言うよりも、複雑さを認識する方がはるかに優れています。それらはアドレスですが、彼が意味するアドレスとは異なる言語で書かれている可能性があります。Cのコンテキストで「住所」と「店舗の住所」の2つを区別することで十分だと思います。


0

単純に言うと、ポインタは実際にはセグメンテーションメカニズムのオフセット部分であり、セグメンテーション後にリニアアドレスに変換され、ページング後に物理アドレスに変換されます。物理アドレスは実際にはRAMからアドレス指定されます。

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.