216

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

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

私が読んだ他の資料(同様に評判の良い情報源から)は、常に、&メモリアドレスを与えるものとしてポインタと演算子を恥ずかしがらずに参照してきました。私は問題の現実を探し続けたいと思いますが、評判の良い情報源の種類が同意しない場合、それはちょっと難しいです。

今、私は少し混乱しています-それでは、メモリアドレスではない場合、ポインタとは正確には何ですか?

PS

著者は後で言います: ...しかし、別の[用語]を発明することはさらに悪いので、私は「のアドレス」という用語を使い続けます。

4

25 に答える 25

155

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

ポインター値は、ある種の ID やハンドル、または複数の ID の組み合わせ (x86 セグメントとオフセットへのハローなど) である可能性があり、必ずしも実際のメモリ アドレスであるとは限りません。この ID は、固定サイズのテキスト文字列であっても、何でもかまいません。非アドレス表現は、C インタープリターにとって特に有用な場合があります。

于 2013-03-01T06:02:16.473 に答える
63

ソースについてはわかりませんが、記述している言語のタイプは C 標準に由来します。

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

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

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

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

編集: Alexey Frunze のコメントを拡張したいと思います。

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

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

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

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

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

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

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

思ったより長くなってしまいました…。

于 2013-03-01T05:57:42.250 に答える
37

ポインターと変数

この写真には、

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

于 2013-03-01T09:31:55.230 に答える
37

ポインターをアドレスと考えるのは近似値です。すべての概算と同様に、場合によっては便利ですが、正確ではないため、それに頼ると問題が発生します。

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

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

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

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

一部の環境では、メモリ内の同じ場所を指す異なる表現 (メモリ内の異なるビット パターン) を持つ複数のポインター値を持つことができます。これらは、同じアドレスを保持する異なるポインター、または同じ場所の異なるアドレスと考えることができます。この場合、比喩は明確ではありません。演算子は==、2 つのオペランドが同じ場所を指しているかどうかを常に通知するため、これらの環境では、異なるビット パターンを持つp == qことができます。pq

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

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

全体として、ポインタをアドレスとして考えることは、次のことを心に留めている限り、それほど悪くはありません。

  • アドレスである有効な非 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とが出力されると予想されるかもしれませんが、これは(リトル エンディアン) または(ビッグエンディアン) のsizeof(short)==2いずれかを出力します。しかし、GCC 4.4 を搭載した私の 64 ビット Linux PC では:1 = 1?65536 = 1?

$ 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 言語のルールが複雑であり、内部で何が起こっているかを知らなければ直感的に理解するのが難しいということです。そして内部で何が起こっているかというと、ポインタとアドレスの間の結びつきは、「エキゾチックな」プロセッサ アーキテクチャをサポートし、コンパイラの最適化をサポートするために、やや緩いということです。

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

于 2013-03-01T20:09:15.563 に答える
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;
}
于 2013-03-01T05:52:59.627 に答える
16

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

書かれているすべての回答から判断すると、一部の人々は、(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) に関する +/- 差)。

于 2013-03-01T07:04:12.373 に答える
15

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

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

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

于 2013-03-01T20:30:44.860 に答える
12

過去に混乱した人々に説明した方法は次のとおりです。ポインターには、その動作に影響を与える 2 つの属性があります。これには、(通常の環境では) メモリ アドレスであるvalueと、それが指すオブジェクトのとサイズを示す type があります。

たとえば、次のようになります。

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++次にアドレス可能な文字を指すためです。通常、整数は文字よりも多くのメモリを消費するため、両方をインクリメントした後よりも大きな値になります。ic++cic

于 2013-03-02T19:15:34.603 に答える
9

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

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

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

于 2013-03-01T05:54:22.480 に答える
8

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

ポインターは、リテラル 3 と同じくらい変数と関係があります。

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

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

変数ではないポインターの反例は、無視するには多すぎます

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

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

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

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

于 2013-03-03T21:14:18.703 に答える
7

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

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

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

于 2013-03-02T21:22:52.270 に答える
6

ポインターは、C/C++ でネイティブに使用できる変数型であり、メモリ アドレスを含みます。他の変数と同様に、独自のアドレスを持ち、メモリを占有します (量はプラットフォームによって異なります)。

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

于 2013-03-01T05:50:54.500 に答える
6

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

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

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

(2) データ メンバへのポインタとメソッドへのポインタは、さらに見慣れないことがよくあります。

(3) FAR および NEAR ポインターの問題を伴うレガシー x86 コード

(4) セキュアな「ファット ポインタ」を備えたいくつかの例、特に IBM AS/400。

私はあなたがもっと見つけることができると確信しています。

詳細:

うーん!これまでの回答の多くは、かなり典型的な「プログラマーのウィニー」の回答ですが、コンパイラーのウィニーやハードウェアのウィニーではありません。私はハードウェアの弱虫のふりをしており、コンパイラの弱虫を扱うことが多いので、2 セントを投入させてください。

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

罰金。

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

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

私たちの多くは、NEAR POINTER と FAR POINTER を使用した、古い Intel x86 セグメント化アーキテクチャの痛ましい思い出を持っています。ありがたいことに、これらは今ではほぼ消滅しているので、簡単に要約すると: 16 ビット リアル モードでは、実際のリニア アドレスは

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

一方、保護モードでは、

LinearAddress = SegmentRegister[SegNum].base + offset

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

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

ここで、私のセキュリティ ソープボックスから離れて、(さまざまな型の) ポインターが実際にはアドレスではないことが多い別の方法について説明します: データ メンバーへのポインター、メンバー関数メソッドへのポインター、およびそれらの静的バージョンは、普通の住所。この投稿 が言うように:

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

おそらく、私のセキュリティに関する (中の) 正当化から推測できるように、私は C/C++ ハードウェア/ソフトウェア プロジェクトに関与してきました。このプロジェクトでは、ポインターは生のアドレスよりも機能のように扱われてきました。

続けることもできますが、理解していただければ幸いです。

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

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

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

(2) データ メンバへのポインタとメソッドへのポインタは、さらに見慣れないことがよくあります。

(3) FAR および NEAR ポインターの問題を伴うレガシー x86 コード

(4) セキュアな「ファット ポインタ」を備えたいくつかの例、特に IBM AS/400。

私はあなたがもっと見つけることができると確信しています。

于 2013-03-15T01:40:25.267 に答える
4

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

于 2013-03-01T05:51:47.413 に答える
4

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

于 2013-03-01T06:04:21.663 に答える
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`

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

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

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

于 2013-03-01T20:39:21.077 に答える
3

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

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

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

ソースコードレベルで移植可能な特別な「ヌルポインター」値がありますが、その表現は異なる場合があります。値が 0 の整数定数をポインターに代入すると、そのポインターはヌル ポインター値になります。そのようにポインタを初期化する場合も同様です。

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

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

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

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

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

于 2013-03-02T08:50:46.793 に答える
3

ポインターは、通常は別の変数のメモリアドレスを格納できる別の変数です。変数であるポインタもメモリアドレスを持っています。

于 2013-03-01T06:16:43.927 に答える
3

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

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

特定の型を持つと解釈されたときのオブジェクトの内容の正確な意味

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

ポインターは同じではありません。ポインターは、C 言語の一種の型です。いくつかの異なるポインター型があります。これらは、言語の同一の規則セットに必ずしも従うとは限り++ませint*char*

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

他のいくつかのことは微妙です。objectC ユーザーとして、まず、次の意味を知っておく必要があります。

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

オブジェクトは、特定の型の値を表すエンティティです。ポインターはオブジェクト型です。したがって、 を宣言する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++ では「address-of」と呼ばれます。同様std::addressofに、C++11 で導入されました。

これらの命名は誤解を招く可能性があります。結果の式はポインター型であるため、次のように解釈されます。結果はアドレスではなく、アドレスを含む/生成します。

于 2014-11-09T07:42:49.707 に答える
2

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

于 2013-03-01T17:05:29.647 に答える
2

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

于 2013-03-01T21:27:09.307 に答える
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*は可能です (移植性は高くありませんが)。

于 2013-07-14T21:03:07.107 に答える
0

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

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical
于 2015-09-06T14:41:29.713 に答える
0

簡単な要約: AC アドレスは値であり、通常はマシンレベルのメモリアドレスとして表され、特定の型を持ちます。

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

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

C標準では、少なくとも場合によっては、「ポインター」という言葉を「ポインター値」を意味するために使用しています。たとえば、mallocの説明では、「null ポインターまたは割り当てられたスペースへのポインターを返す」と述べています。

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

標準の単項演算子の説明では、&「オペランドのアドレスを生成する」と述べています。C 標準以外では、"アドレス" という単語は (物理的または仮想的な) メモリ アドレスを参照するために一般的に使用されます。

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

于 2013-07-15T01:40:32.440 に答える
0

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

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

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

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

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

個人的には、アセンブリ プログラマーに C を導入するという文脈を除いて、著者の発言はそれほど役立つとは思いません。高水準言語から来ている人にとって、ポインター値はアドレスではないと言うことは確かに役に立ちません。CPU がアドレスとは何かを独占しており、したがって C ポインタ値はアドレス「ではない」と言うよりも、複雑さを認める方がはるかに良いでしょう。それらは住所ですが、彼が意味する住所とは異なる言語で書かれている可能性があります。C の文脈では、「アドレス」と「ストア アドレス」の 2 つを区別するのが適切だと思います。

于 2014-02-08T11:16:19.233 に答える