Cに関する評判のよい情報源では、&
演算子について説明した後、次の情報が提供されます。
...用語[address of]が残っているのは少し残念です。これは、アドレスが何であるかを知らない人を混乱させ、誤解を招くためです。行う人:ポインタをアドレスであるかのように考えると、通常は悲しみにつながります...
私が読んだ他の資料(同じように評判の良い情報源から)は、常にポインターと&
演算子をメモリアドレスを与えると恥ずかしがらずに言及していました。 私は問題の現実を探し続けたいと思うが、評判の良い情報源が反対するときはちょっと難しい。
今、私は少し混乱しています-何exactlyはポインターですか?
P.S。
著者は後で言います:...しかし、私は「アドレス」という用語を使い続けます、なぜなら異なるものを発明するためです[用語]さらに悪化します。
C標準は、ポインターが内部的に何であるか、およびポインターが内部でどのように機能するかを定義していません。これは、プラットフォームの数を制限しないための意図的なものであり、Cはコンパイル済みまたは解釈済みの言語として実装できます。
ポインター値は、何らかのIDまたはハンドル、または複数のIDの組み合わせ(x86セグメントとオフセットなど)であり、必ずしも実際のメモリアドレスであるとは限りません。このIDは、固定サイズのテキスト文字列でも何でもかまいません。非アドレス表現は、Cインタープリターにとって特に有用です。
あなたのソースについてはわかりませんが、あなたが記述している言語のタイプはC標準から来ています:
6.5.3.2アドレスおよび間接演算子
[...]
3。 単項&演算子は、オペランドのアドレスを生成します。 [...]
だから...ええ、ポインタはメモリアドレスを指します。少なくともそれは、C標準が意味することを示唆している方法です。
もう少し明確に言うと、ポインターはvalueの一部のaddressを保持する変数です。オブジェクトのアドレス(ポインターに格納される場合があります)は、単項&
演算子で返されます。
アドレスに「42 Wallaby Way、Sydney」という変数を格納できます(その変数は一種の「ポインター」になりますが、それはメモリアドレスではないため、「ポインター」と呼ぶのは適切ではありません)。コンピューターには、メモリのバケットのアドレスがあります。ポインターはアドレスの値を格納します(つまり、ポインターは値である「42 Wallaby Way、Sydney」、アドレスです)。
編集: Alexey Frunzeのコメントを拡張したい。
ポインタとは正確には何ですか? C標準を見てみましょう。
6.2.5タイプ
[...]
20。 [...]
A ポインタ型は、参照型と呼ばれる関数型またはオブジェクト型から派生できます。ポインター型は、その値が参照される型のエンティティへの参照を提供するオブジェクトを記述します。参照された型Tから派生したポインター型は、「Tへのポインター」と呼ばれることもあります。参照型からのポインター型の構築は、「「ポインター型の派生」」と呼ばれます。ポインタ型は完全なオブジェクト型です。
基本的に、ポインターには、オブジェクトまたは関数への参照を提供する値が格納されます。やや。ポインターは、オブジェクトまたは関数への参照を提供する値を格納することを目的としていますが、それはalwaysではありません:
6.3.2.3ポインター
[...]
5。整数は、任意のポインター型に変換できます。前に指定した場合を除き、結果は実装定義であり、正しく位置合わせされていない可能性があり、参照された型のエンティティを指していない可能性があり、トラップ表現である可能性があります。
上記の引用は、整数をポインタに変えることができると言っています。それを行う場合(つまり、オブジェクトまたは関数への特定の参照の代わりに整数値をポインターに挿入する場合)、ポインターは「参照型のエンティティを指し示していない可能性があります」(つまり、オブジェクトまたは関数への参照)。それは私たちに何か他のものを提供するかもしれません。そして、これはある種のハンドルまたはIDをポインターに固定する場所の1つです(つまり、ポインターはオブジェクトを指していません。何かを表す値を格納していますが、その値はアドレスではない場合があります)。
そうです、Alexey Frunzeが言うように、ポインターがオブジェクトや関数へのアドレスを保存していない可能性があります。代わりに、ポインターが何らかの「ハンドル」またはIDを格納している可能性があります。これを行うには、ポインターに任意の整数値を割り当てます。このハンドルまたはIDが表すものは、システム/環境/コンテキストによって異なります。システム/実装が価値を理解できる限り、体調は良好です(ただし、特定の値と特定のシステム/実装に依存します)。
通常、ポインタはオブジェクトまたは関数へのアドレスを格納します。実際のアドレスを(オブジェクトまたは関数に)格納していない場合、結果は実装定義です(つまり、実際に何が発生し、ポインターが何を表すかは、システムと実装に依存するため、ハンドルまたはIDになります)特定のシステムですが、別のシステムで同じコード/値を使用すると、プログラムがクラッシュする可能性があります)。
思ったよりも長くなってしまいました...
この写真には、
pointer_pは、0x12345にあるポインターで、0x34567の変数variable_vを指します。
ポインターをアドレスと考えることは、approximationです。すべての近似値と同様に、それは時々役立つのに十分ですが、正確ではないため、それに依存すると問題が発生することを意味します。
ポインターは、オブジェクトを見つける場所を示すという点でアドレスのようなものです。この類推の直接的な制限の1つは、すべてのポインターに実際にアドレスが含まれているわけではないことです。 NULL
は、アドレスではないポインターです。ポインター変数の内容は、実際には次の3種類のいずれかです。
p
にx
のアドレスが含まれている場合、式*p
にはx
)と同じ値;NULL
は例です。p
が有効な値を保持していない場合、*p
は何でもできます( 「未定義の動作」)、プログラムのクラッシュはかなり一般的な可能性)。さらに、ポインター(有効かつ非ヌルの場合)にはアドレスが含まれると言う方が正確です:ポインターはオブジェクトを見つける場所を示しますが、より多くの情報がありますそれに縛ら。
特に、ポインターには型があります。ほとんどのプラットフォームでは、ポインターの型は実行時には影響しませんが、コンパイル時には型を超える影響があります。 p
がint
(int *p;
)へのポインターである場合、p + 1
は、p
の後のsizeof(int)
バイトの整数を指します(p + 1
がまだ有効なポインターであると想定)。 q
がchar
(char *q = p;
)と同じアドレスを指すp
へのポインターである場合、q + 1
はp + 1
と同じアドレスではありません。ポインタをアドレスと考えると、同じ場所への異なるポインタで「次のアドレス」が異なることはあまり直感的ではありません。
一部の環境では、メモリ内の同じ場所を指す異なる表現(メモリ内の異なるビットパターン)を持つ複数のポインタ値を持つことができます。これらは、同じアドレスを保持する異なるポインター、または同じ場所の異なるアドレスと考えることができます。この場合、比phorは明確ではありません。 ==
演算子は、2つのオペランドが同じ場所を指しているかどうかを常に示すため、これらの環境では、p
とq
のビットパターンが異なっていてもp == q
を使用できます。
タイプや許可情報など、ポインターがアドレス以外の情報を運ぶ環境もあります。これらに遭遇することなく、プログラマーとしての人生を簡単に経験できます。
異なる種類のポインターが異なる表現を持つ環境があります。これは、異なる表現を持つ異なる種類のアドレスと考えることができます。たとえば、一部のアーキテクチャには、バイトポインターとWordポインター、またはオブジェクトポインターと関数ポインターがあります。
全体として、ポインターをアドレスとして考えることは、次のことを念頭に置いていればそれほど悪くはありません。
逆方向に行くのははるかに面倒です。 アドレスのように見えるものすべてがポインタになれるわけではありません。ポインタの深いところのどこかは、整数として読み取ることができるビットパターンとして表され、この整数はアドレスであると言えます。しかし、逆に、すべての整数がポインターであるとは限りません。
最初にいくつかのよく知られた制限があります。たとえば、プログラムのアドレス空間外の場所を指定する整数は、有効なポインターにはできません。位置合わせ不良のアドレスは、位置合わせが必要なデータ型に対して有効なポインターを作成しません。たとえば、int
が4バイトのアライメントを必要とするプラットフォームでは、0x7654321を有効なint*
値にすることはできません。
ただし、整数へのポインターを作成すると、トラブルの世界に陥るので、それはそれをはるかに超えています。この問題の大きな部分は、ほとんどのプログラマーが予想するよりも最適化コンパイラーがマイクロ最適化の方がはるかに優れているため、プログラムの動作方法のメンタルモデルが非常に間違っているということです。同じアドレスのポインターがあるからといって、それらが同等であることを意味するわけではありません。たとえば、次のスニペットを考えます。
unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);
sizeof(int)==4
と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
は&x
と異なるタイプであるため、p
が指すものを変更しても、&x
が指すものに影響を与えることはできません(いくつかの明確に定義された例外以外)。したがって、コンパイラはx
の値をレジスタに保持し、*p
の変更に応じてこのレジスタを更新しないようにします。プログラムは、同じアドレスへの2つのポインターを逆参照し、2つの異なる値を取得します!
この例の教訓は、C言語の正確な規則の範囲内にある限り、(nullでない有効な)ポインターをアドレスとして考えることは問題ないということです。コインの裏側は、C言語のルールが複雑であり、フードの下で何が起こるかを知らない限り、直感的な感覚を得るのが難しいということです。そして、内部で行われることは、「エキゾチックな」プロセッサアーキテクチャをサポートし、コンパイラを最適化することをサポートするために、ポインタとアドレスの結び付きがやや緩いことです。
したがって、ポインタはアドレスであると理解の第一歩として考えてください。ただし、その直感にあまり従わないでください。
ポインターは、アドレス自体ではなく、メモリアドレスを保持する変数です。ただし、ポインターを逆参照することができます-とメモリの場所へのアクセスを取得します。
例えば:
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*/
それでおしまい。とても簡単です。
私が言っていることを実証するプログラムとその出力は次のとおりです。
プログラム:
#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;
}
それらの本の著者が正確に何を意味するかを正確に伝えることは困難です。ポインターにアドレスが含まれるかどうかは、アドレスの定義方法とポインターの定義方法によって異なります。
書かれているすべての答えから判断すると、(1)アドレスは整数でなければならず、(2)ポインタは仕様でそう言われないことによって仮想である必要はないと想定する人もいます。これらの仮定では、ポインターには必ずしもアドレスが含まれているとは限りません。
ただし、(2)はおそらく真実ですが、(1)はおそらく真実である必要はありません。そして、@ 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)の点で+/-の差)。
さて、ポインタはメモリ位置を表すabstractionです。引用では、ポインタをメモリアドレスであるかのように考えることは間違っているとは言わず、単に「通常は悲嘆につながる」とだけ言っていることに注意してください。言い換えれば、それは間違った期待を抱かせます。
悲しみの最もありそうな原因は、確かにポインター演算です。これは実際にはCの長所の1つです。ポインターがアドレスの場合、ポインター算術演算はアドレス算術演算であると予想されます。しかし、そうではありません。たとえば、アドレスに10を追加すると、10アドレッシング単位だけ大きいアドレスが得られます。ただし、ポインタに10を追加すると、ポインタが指す種類のオブジェクトのサイズの10倍だけ増加します(実際のサイズではなく、境界線に切り上げられます)。 32ビット整数を使用する通常のアーキテクチャでint *
を使用すると、10を追加すると、アドレス単位(バイト)が40増加します。経験豊富なCプログラマーはこれを認識しており、一緒に暮らしていますが、著者は明らかにずさんなメタファーのファンではありません。
ポインタの内容がどのようにどのように表示されるかという追加の質問があります:メモリ位置:回答で説明したように、アドレスは必ずしもint(またはlong)ではありません。一部のアーキテクチャでは、アドレスは「セグメント」にオフセットを加えたものです。ポインターには、現在のセグメントへのオフセット(「ニア」ポインター)のみが含まれている場合がありますが、それ自体は一意のメモリアドレスではありません。また、ポインタの内容は、ハードウェアが理解するため、メモリアドレスと間接的な関係しかない場合があります。しかし、引用された引用の著者は表現についても言及していないので、彼らが念頭に置いていたのは、表現ではなく概念的な同等性だったと思います。
過去に混乱した人々に説明した方法は次のとおりです。ポインターには、その動作に影響する2つの属性があります。 値(通常の環境では)のメモリアドレスと、typeがあり、それが指すオブジェクトのタイプとサイズを示します。
たとえば、次の場合:
union {
int i;
char c;
} u;
この同じオブジェクトをすべて指す3つの異なるポインターを使用できます。
void *v = &u;
int *i = &u.i;
char *c = &u.c;
これらのポインターの値を比較すると、それらはすべて等しくなります。
v==i && i==c
ただし、各ポインターをインクリメントすると、ポインターが指しているtypeが関連することがわかります。
i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c
変数i
とc
は、この時点で異なる値になります。これは、i++
によりi
に次のアクセス可能な整数のアドレスが含まれ、c++
c
が次のアドレス指定可能な文字を指すようにします。通常、整数は文字よりも多くのメモリを使用するため、i
は両方とも増加した後、c
よりも大きな値になります。
マーク・ベッシーはすでに言っていますが、これは理解されるまで再強調する必要があります。
ポインターは、リテラル3よりも変数に関係しています。
ポインタis(アドレスの)値とタイプ(読み取り専用などの追加プロパティを含む)のタプル。タイプ(および存在する場合は追加のパラメーター)は、コンテキストをさらに定義または制限できます。例えば。 __far ptr, __near ptr
:アドレスのコンテキスト:スタック、ヒープ、線形アドレス、どこからのオフセット、物理メモリ、または何。
typeのプロパティが、ポインタ演算を整数演算と少し異なるものにします。
変数ではないポインターの反例は無視するには多すぎる
通常はアドレス指定できないレジスタであるスタックポインターまたはフレームポインター
*(int *)0x1231330 = 13;
-任意の整数値をpointer_of_integer型にキャストし、変数を導入することなく整数を読み書きします
Cプログラムの存続期間には、アドレスを持たない一時ポインターの他の多くのインスタンスがあります。したがって、それらは変数ではなく、コンパイル時関連型の式/値です。
ポインターは、Cの他の変数と同様に、基本的に1つ以上の連結unsigned char
値で表されるビットのコレクションです(他の種類のcariableと同様に、sizeof(some_variable)
はunsigned char
値)。ポインターが他の変数と異なるのは、Cコンパイラーがポインターのビットを、何らかの方法で変数が格納される場所を識別するものとして解釈することです。 Cでは、他の言語とは異なり、複数の変数のスペースを要求し、そのセット内の任意の値へのポインターをそのセット内の他の変数へのポインターに変換することができます。
多くのコンパイラは、実際のマシンアドレスを格納するビットを使用してポインタを実装しますが、それが唯一の可能な実装ではありません。実装は、プログラムが使用していたすべてのメモリオブジェクト(変数のセット)のハードウェアアドレスと割り当てられたサイズをリストし、ユーザーコードにアクセスできない1つの配列を保持し、各ポインターに配列へのインデックスを含めることができますそのインデックスからのオフセットで。このような設計により、システムは、所有するメモリ上でのみ動作するようにコードを制限するだけでなく、1つのメモリ項目へのポインタが誤って別のメモリ項目へのポインタに変換されないことを保証します(ハードウェアを使用するシステムでfoo
およびbar
がメモリに連続して格納される10個のアイテムの配列である場合、foo
の「11番目」のアイテムへのポインターは、代わりにbar
ですが、各「ポインター」がオブジェクトIDとオフセットであるシステムでは、コードが割り当てられた範囲を超えてfoo
へのポインターをインデックス付けしようとした場合、システムがトラップする可能性があります。また、このようなシステムでは、任意のポインターに関連付けられた物理アドレスを移動できるため、メモリフラグメンテーションの問題を解消することも可能です。
ポインタはやや抽象的ですが、完全に標準に準拠したCコンパイラでガベージコレクタを実装できるほど抽象的ではありません。 Cコンパイラは、ポインターを含むすべての変数がunsigned char
値のシーケンスとして表されることを指定します。任意の変数が与えられると、それを一連の数字に分解し、後でその一連の数字を元の型の変数に変換することができます。その結果、プログラムがcalloc
何らかのストレージ(それへのポインターを受け取る)、そこに何かを格納し、ポインターを一連のバイトに分解し、画面に表示し、すべての参照を消去することが可能になりますそれら。プログラムがキーボードからいくつかの数字を受け入れ、それらをポインターに再構成し、そのポインターからデータを読み取ろうとした場合、ユーザーがプログラムが以前に表示したのと同じ数字を入力した場合、プログラムはデータを出力する必要がありますcalloc
'edメモリに保存されていました。ユーザーが表示された数字のコピーを作成したかどうかをコンピューターが知る方法は考えられないため、コンピューターが前述のメモリが将来アクセスされる可能性があるかどうかを知ることは考えられません。
ポインターは、C/C++でネイティブに使用可能な変数タイプであり、メモリアドレスが含まれています。他の変数と同様に、独自のアドレスを持ち、メモリを占有します(量はプラットフォーム固有です)。
混乱の結果として表示される問題の1つは、値でポインタを渡すだけで、関数内のリファレントを変更しようとすることです。これにより、関数スコープでポインターのコピーが作成され、この新しいポインターが「ポイント」する場所への変更は、関数を呼び出したスコープでポインターの参照先を変更しません。関数内の実際のポインターを変更するには、通常、ポインターをポインターに渡します。
BRIEF SUMMARY(これも先頭に置きます):
(0)ポインターをアドレスとして考えることは、多くの場合、優れた学習ツールであり、通常のデータ型へのポインターの実際の実装です。
(1)しかし、多くの場合、おそらくほとんどの場合、コンパイラーの関数へのポインターはアドレスではなく、アドレス(通常は2x、時にはそれ以上)よりも大きいか、実際には関数のアドレスなどを含むメモリ内の構造体へのポインターです定数プール。
(2)データメンバへのポインタとメソッドへのポインタは、しばしば見知らぬものです。
(3)FARおよびNEARポインターの問題があるレガシーx86コード
(4)安全な「ファットポインター」を使用したいくつかの例、特にIBM AS/400。
もっと見つけられると思います。
詳細:
UMMPPHHH !!!!!これまでの回答の多くは、かなり典型的な「プログラマーウィニー」の回答ですが、コンパイラーウィニーやハードウェアウィニーではありません。私はハードウェアウィニーのふりをして、コンパイラウィニーとよく仕事をするので、2セントを投入させてください。
多くの、おそらくほとんどのCコンパイラでは、T
型のデータへのポインターは、実際にはT
のアドレスです。
いいよ.
しかし、これらのコンパイラの多くでも、特定のポインターはアドレスではありません。これは、sizeof(ThePointer)
を見ればわかります。
たとえば、関数へのポインタは、通常のアドレスよりもかなり大きい場合があります。または、間接レベルが関係する場合があります。 この記事 はIntel Itaniumプロセッサに関する1つの説明を提供しますが、他にも説明があります。通常、関数を呼び出すには、関数コードのアドレスだけでなく、関数の定数プールのアドレスも知っている必要があります-コンパイラが生成する必要があるのではなく、単一のロード命令で定数がロードされるメモリの領域いくつかのLoad ImmediateおよびShiftおよびOR命令からの64ビット定数。したがって、単一の64ビットアドレスではなく、2つの64ビットアドレスが必要です。一部のABI(Application Binary Interfaces)はこれを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
---と言いましたが、コンパイラーとリンカーのスイッチがあったため、たとえば、コードポインターはポインターの近く、わずか32ビットデータポインターはFARポインターである可能性がありますが、CS(コードセグメント)レジスタにあるものに対するオフセットは、16ビットセグメント番号と48ビット値の32ビットオフセットの両方を指定します。さて、これらの両方の数量は確かに住所に関連していますが、それらは同じサイズではないので、どちらが住所ですか?さらに、セグメントには、実際のアドレスに関連するものに加えて、アクセス許可(読み取り専用、読み取り/書き込み、実行可能)も含まれていました。
より興味深い例であるIMHOは、IBM AS/400ファミリーです(または、おそらくそうでした)。このコンピューターは、C++でOSを実装した最初のコンピューターの1つです。このmachimeのポインタは通常、実際のアドレスサイズの2倍でした。 as このプレゼンテーション は、128ビットポインターですが、実際のアドレスは48-64ビットであり、また、読み取り、書き込み、asなどのアクセス許可を提供する追加情報、機能と呼ばれるものですバッファオーバーフローを防ぐための制限もあります。はい:これはC/C++と互換性があります-これが遍在する場合、中国人民解放軍とスラブマフィアは多くの西洋のコンピューターシステムをハッキングしません。しかし、歴史的に、ほとんどのC/C++プログラミングはパフォーマンスのセキュリティを無視していました。最も興味深いことに、AS400ファミリは、オペレーティングシステムが安全なポインターを作成できるようにしました。安全なポインターは、特権のないコードに与えることができますが、特権のないコードは偽造または改ざんできません。繰り返しますが、セキュリティ、および標準に準拠していますが、多くのずさんな非標準に準拠したC/C++コードは、このような安全なシステムでは機能しません。繰り返しますが、公式の標準があり、事実上の標準があります。
ここで、セキュリティソープボックスから降りて、(さまざまなタイプの)ポインターが実際にアドレスされない他のいくつかの方法に言及します:データメンバーへのポインター、メンバー関数メソッドへのポインター、およびその静的バージョンは通常の住所。 この投稿 のように:
これを解決する方法は多数あります[単一の継承と複数の継承、および仮想継承に関連する問題]。 Visual Studioコンパイラがそれを処理する方法を以下に示します。多重継承クラスのメンバー関数へのポインターは、実際には構造体です。」そして、「関数ポインターをキャストするとそのサイズが変わる可能性があります!」.
おそらくセキュリティの(イン)セキュリティに関する知識から推測できるように、ポインターが生のアドレスではなく機能のように扱われるC/C++ハードウェア/ソフトウェアプロジェクトに関与してきました。
私は続けることができますが、私はあなたがアイデアを得ると思います。
BRIEF SUMMARY(これも先頭に置きます):
(0)ポインターをアドレスとして考えることは、多くの場合、優れた学習ツールであり、通常のデータ型へのポインターの実際の実装です。
(1)しかし、多くの場合、おそらくほとんどの場合、コンパイラーの関数へのポインターはアドレスではなく、アドレス(通常は2X、時にはそれ以上)よりも大きいか、実際には関数のアドレスなどを含むメモリ内の構造体へのポインターです定数プール。
(2)データメンバへのポインタとメソッドへのポインタは、しばしば見知らぬものです。
(3)FARおよびNEARポインターの問題があるレガシーx86コード
(4)安全な「ファットポインター」を使用したいくつかの例、特にIBM AS/400。
もっと見つけられると思います。
ポインタは、メモリロケーションのアドレス(通常は別の変数のメモリアドレス)を保持するために使用される別の変数です。
このように見ることができます。ポインターは、アドレス可能なメモリ空間内のアドレスを表す値です。
ポインターを理解する前に、オブジェクトを理解する必要があります。オブジェクトは存在するエンティティであり、アドレスと呼ばれる場所指定子を持っています。ポインターは、次の操作をサポートするオブジェクトのアドレスとしてコンテンツが解釈される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`
ポインターは、現在参照しているオブジェクトのタイプに基づいて分類されます。重要な情報の唯一の部分は、オブジェクトのサイズです。
すべてのオブジェクトは、&
(のアドレス)操作をサポートします。この操作は、オブジェクトの位置指定子(アドレス)をポインターオブジェクトタイプとして取得します。これは、結果の型がオブジェクト型のポインターであるポインターではなく、オブジェクトの操作として&
を呼び出すのが理にかなっているため、命名法を取り巻く混乱を軽減するはずです。
注この説明では、メモリの概念は省略しています。
ポインタは、通常は別の変数のメモリアドレスを含むことができる別の変数です。ポインターは変数でもあり、メモリーアドレスを持っています。
アドレスは、固定サイズのストレージを、通常は各バイトごとに整数として識別するために使用されます。これはbyte addressと正確に呼ばれ、ISO Cでも使用されます。アドレスを作成する方法は他にもあります。ビットごとに。ただし、バイトアドレスのみが頻繁に使用されるため、通常は「バイト」を省略します。
(ISO)Cの用語「値」の定義は次のとおりであるため、技術的には、アドレスがCの値になることはありません。
特定のタイプを持つと解釈された場合のオブジェクトの内容の正確な意味
(私は強調しています。)しかし、Cにはそのような「アドレスタイプ」はありません。
ポインターが同じではありません。ポインターは、C言語のtypeの一種です。いくつかの異なるポインタータイプがあります。彼らは必ずしも言語の同一のルールのセットに従うわけではありません。タイプ++
とint*
の値に対するchar*
の効果。
Cの値は、ポインター型にすることができます。これは、ポインタ値と呼ばれます。明確にするために、ポインター値はC言語のポインターではありません。しかし、Cでは曖昧になる可能性は低いため、これらを混ぜることに慣れています。式p
を「ポインター」として呼び出す場合、それは単なるポインター値であり、型ではありません。 Cのtypeは、expressionではなく、type-nameまたはtypedef-name。
他のいくつかは微妙です。 Cユーザーとして、まず、object
の意味を理解する必要があります。
実行環境内のデータストレージの領域。その内容は値を表すことができます
オブジェクトは、特定のタイプの値を表すエンティティです。ポインターは、オブジェクトタイプです。したがって、int* p;
を宣言すると、p
は「ポインター型のオブジェクト」または「ポインターオブジェクト」を意味します。
no "variable"が標準で規範的に定義されていることに注意してください(実際、ISO Cでは規範テキストで名詞として使用されることはありません)。ただし、非公式には、他の言語と同様に、オブジェクトを変数と呼びます。 (しかし、それほど正確ではありません。たとえば、C++では、変数はreference型であり、オブジェクトではありません。)「ポインタオブジェクト」というフレーズまたは「ポインタ変数」は、上記のように「ポインタ値」のように扱われることがありますが、わずかな違いがあります。 (もう1つの例は「配列」です。)
ポインターは型であり、アドレスはCでは事実上「型なし」なので、ポインター値はおおよそアドレスを「含んでいます」。そして、ポインタ型の式は、アドレスをyieldすることができます。
ISO C11 6.5.2.
3単項
&
演算子は、オペランドのアドレスを生成します。
この表現はWG14/N1256、つまりISO C99:TC3で導入されていることに注意してください。 C99には
3単項
&
演算子は、オペランドのアドレスを返します。
これは委員会の意見を反映しています。アドレスはnot単項&
演算子によって返されるポインター値です。
上記の文言にも関わらず、標準にも混乱が残っています。
ISO C11 6.6
9アドレス定数は、nullポインタ、静的ストレージ期間のオブジェクトを指定する左辺値へのポインタ、または関数指定子へのポインタです。
ISO C++ 11 5.19
3 ...アドレス定数式は、静的ストレージ期間を持つオブジェクトのアドレスを評価するポインター型のprvalueコア定数式です。関数のアドレス、nullポインタ値、または
std::nullptr_t
型のprvalueコア定数式。 ...
(最近のC++標準ドラフトでは別の表現を使用しているため、この問題はありません。)
実際、Cの「アドレス定数」とC++の「アドレス定数式」の両方は、ポインター型(または、C++ 11以降の「ポインターのような」型)の定数式です。
そして、組み込みの単項&
演算子は、CおよびC++では「アドレス」として呼び出されます。同様に、std::addressof
はC++ 11で導入されました。
これらの命名は誤解を招く可能性があります。結果の式はポインター型であるため、結果はisアドレスではなく、アドレスを含む/生成します。
Cポインターはメモリアドレスに非常に似ていますが、マシン依存の詳細が抽象化されており、下位レベルの命令セットにはない機能もあります。
たとえば、Cポインターは比較的豊富に入力されます。構造体の配列を介してポインタをインクリメントすると、構造体から他の構造体にうまくジャンプします。
ポインターは変換規則に従い、コンパイル時の型チェックを提供します。
ソースコードレベルでは移植可能ですが、表現が異なる場合がある特別な「ヌルポインター」値があります。値がゼロの整数定数をポインターに割り当てると、そのポインターはNULLポインター値を取ります。ポインタをそのように初期化する場合も同じです。
ポインターはブール変数として使用できます。null以外の場合はtrue、nullの場合はfalseをテストします。
機械語では、nullポインターが0xFFFFFFFFのような面白いアドレスである場合、その値の明示的なテストが必要になる場合があります。 Cはあなたからそれを隠します。 nullポインターが0xFFFFFFFFであっても、if (ptr != 0) { /* not null! */}
を使用してテストできます。
型システムを破壊するポインターを使用すると、未定義の動作が発生しますが、機械語の同様のコードは適切に定義される場合があります。アセンブラーは、作成した命令をアセンブルしますが、Cコンパイラーは、ユーザーが何も間違ったことをしていないという仮定に基づいて最適化します。 float *p
ポインターがlong n
変数を指し、*p = 0.0
が実行される場合、コンパイラーはこれを処理する必要はありません。その後のn
の使用は、フロート値のビットパターンを読み取る必要はありませんが、おそらく、n
が変更されていないという「厳密なエイリアシング」の仮定に基づく最適化されたアクセスになります。 !つまり、プログラムが正常に動作しているという前提、したがってp
はn
を指してはなりません。
Cでは、コードへのポインターとデータへのポインターは異なりますが、多くのアーキテクチャーでは、アドレスは同じです。ターゲットアーキテクチャにはないものの、「脂肪」ポインタを持つCコンパイラを開発できます。ファットポインターとは、ポインターが単なるマシンアドレスではなく、境界チェックのために、ポイントされているオブジェクトのサイズに関する情報など、他の情報を含むことを意味します。移植可能なプログラムは、このようなコンパイラに簡単に移植できます。
ご覧のとおり、マシンアドレスとCポインターには多くの意味上の違いがあります。
「アドレスが何であるかを知らない人を混乱させるため」と言っています-また、それは事実です。アドレスが何であるかを学べば、混乱しないでしょう。理論的には、ポインターは別のものを指す変数であり、実際にはアドレスを保持します。アドレスは、それが指す変数のアドレスです。 hideこの事実がなぜ必要なのかはわかりませんが、それはロケット科学ではありません。ポインターを理解すれば、コンピューターがどのように機能するかを理解するために一歩近づきます。どうぞ!
考えてみると、セマンティクスの問題だと思います。 C標準では、他の人がすでにここで言及しているように、参照されたオブジェクトへのアドレスを保持するものとしてポインターを参照しているため、著者は正しいとは思いません。ただし、address!=メモリアドレス。アドレスは実際にはC標準に従って何でもかまいませんが、最終的にはメモリアドレスにつながります。ポインター自体はid、オフセット+セレクター(x86)、実際に記述できる限り(マッピング後)になります- anyアドレス可能スペースのメモリアドレス。
CまたはC++ポインターが他の回答では見られなかったさまざまなポインタータイプのために単純なメモリアドレスと異なるもう1つの方法(合計サイズを考えても、見落としているかもしれません)。しかし、おそらく最も重要なのは、経験豊富なC/C++プログラマーでさえトリップすることができるためです。
コンパイラーは、互換性のないタイプのポインターは、たとえ明確に指定しても同じアドレスを指さないと仮定する場合があり、単純なポインター==アドレスモデルでは不可能な動作を与える場合があります。次のコードを考慮してください(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*
を使用して値を操作することは可能です(ただし、移植性はあまりありません)。
簡単に言うと、ポインタは実際にはセグメンテーションメカニズムのオフセット部分であり、セグメンテーション後にリニアアドレスに変換され、ページング後に物理アドレスに変換されます。物理アドレスは、実際にはRAMからアドレス指定されます。
Selector +--------------+ +-----------+
---------->| | | |
| Segmentation | ------->| Paging |
Offset | Mechanism | | Mechanism |
---------->| | | |
+--------------+ +-----------+
Virtual Linear Physical
概要:Cアドレスは値であり、通常は特定のタイプのマシンレベルのメモリアドレスとして表されます。
修飾されていない単語「ポインタ」はあいまいです。 Cにはポインターオブジェクト(変数)、ポインターtypes、ポインターexpressions、およびpointervalues。
「ポインタ」という言葉を「ポインタオブジェクト」という意味で使用することは非常に一般的であり、混乱を招く可能性があります。そのため、「ポインタ」を名詞ではなく形容詞として使用しようとしています。
C標準では、少なくともいくつかの場合、「ポインター」という単語を使用して「ポインター値」を意味します。たとえば、mallocの説明は、「nullポインターまたは割り当てられたスペースへのポインターのいずれかを返す」ことを示しています。
それでは、Cのアドレスは何ですか?これはポインター値、つまり特定のポインター型の値です。 (nullポインター値は、必ずしも「アドレス」と呼ばれるわけではありませんが、何のアドレスでもないためです)。
単項&
演算子の規格の説明では、「オペランドのアドレスを取得する」と書かれています。 C標準以外では、Wordの「アドレス」は一般に(物理または仮想)メモリアドレスを参照するために使用され、通常は1ワードのサイズ(特定のシステム上にある「Word」)を表します。
Cの「アドレス」は通常、マシンアドレスとして実装されます。ちょうどC int
値がマシンワードとして実装されるのと同じです。しかし、Cアドレス(ポインター値)は単なるマシンアドレス以上のものです。これは通常、マシンアドレスとして表される値であり、特定のタイプ。
ポインター値isアドレス。ポインター変数isアドレスを格納できるオブジェクト。これは、標準がポインターを定義するものだからです。 Cの初心者は、ポインターとポインターが指すものの違いについてよくわからないことが多いため、Cの初心者に伝えることが重要です(つまり、エンベロープと建物の違いがわからない)。アドレス(すべてのオブジェクトにアドレスがあり、ポインターが保存するもの)の概念は、それを整理するため重要です。
ただし、標準は特定の抽象化レベルで話します。著者は、「アドレスが何であるかを知っている」が、Cに慣れていない人については、おそらくアセンブリ言語をプログラミングすることにより、異なる抽象化レベルでアドレスについて学んだはずです。 C実装がCPUのオペコードが使用するアドレス(この節では「ストアアドレス」と呼ばれる)と同じ表現を使用するという保証はありません。
彼は続けて「完全に合理的なアドレス操作」について話しています。 C標準に関する限り、「完全に合理的なアドレス操作」のようなものは基本的にありません。加算はポインターで定義され、それは基本的にそれです。もちろん、ポインターを整数に変換し、ビット演算または算術演算を実行してから、元に戻すことができます。これは標準で動作することが保証されていないため、そのコードを記述する前に、特定のC実装がどのようにポインターを表し、その変換を実行するかをよく理解する必要があります。 おそらくは期待するアドレス表現を使用しますが、マニュアルを読んでいなかったのであなたのせいではありません。それは混乱ではなく、間違ったプログラミング手順です;-)
要するに、Cは著者よりも抽象的なアドレス概念を使用しています。
もちろん、著者のアドレスの概念は、この問題に関する最低レベルのWordではありません。仮想メモリマップと複数のチップにまたがる物理RAMアドレス指定で、CPUにアクセスする「ストアアドレス」であると伝える数値は、基本的に、必要なデータが実際にある場所とは関係ありませんハードウェアで。それはすべて間接と表現の層ですが、著者は特権に1つを選択しました。 Cについて話すときにそうするなら、Cレベルを特権に選択してください!
個人的には、CをAssemblyプログラマーに紹介する文脈を除いて、著者の発言がそれほど役立つとは思いません。ポインター値はアドレスではないと言うことは、高レベルの言語から来た人にとって確かに役に立たない。 CPUがアドレスとは何かを独占しているため、Cポインタ値はアドレスではないというよりも、複雑さを認める方がはるかに良いでしょう。それらは住所ですが、彼が意味する住所とは異なる言語で書かれているかもしれません。 Cのコンテキストで2つのことを「アドレス」と「ストアアドレス」として区別するのが適切だと思います。