15

Pointer Arithmeticで少し読んでいたのですが、理解できないことが2つあり、その使い方もわかりませんでした。

address_expression - address_expression

そしてまた

address_expression > address_expression

誰かが私にそれらを説明してもらえますか、それらはどのように機能し、いつ使用されるのですか?

編集:

私が言いたかったのは、2つのアドレスを取得してそれらを減算すると、それらは何を生成するかということです。

そして、2つのアドレスを取得して比較すると、結果はどうなりますか、またはそれに基づいて比較します

編集:アドレスを減算した結果は理解できましたが、アドレスを比較してもわかりません。

1 <2であることは理解していますが、アドレスが他のアドレスよりもどのように大きいか、およびそれらは何と比較されますか

4

7 に答える 7

35

ここでのいくつかの回答は、ポインターは数値であると述べています。これは、C 標準で指定されているポインターの正確な説明ではありません。

大部分は、(a) ポインター減算がバイトから (減算されるポインターの型の) 要素に違いを変換することを理解していれば、ポインターを数値およびメモリ内のアドレスと考えることができます。このモデルが壊れる限界を理解してください。

以下では、1999 C 標準 (ISO/IEC 9899、第 2 版、1999-12-01) を使用しています。以下は質問者が要求したよりも詳細であると思いますが、ここにいくつかの虚偽の記載があることを考えると、正確かつ正確な情報を提供する必要があると判断します.

6.5.6 パラグラフ 9 に従って、同じ配列の要素または配列の最後の要素の 1 つ後ろを指す 2 つのポインターを減算できます。したがって、 がある場合int a[8], b[4];、a[5] と a[2] は同じ配列の要素であるため、a[2] へのポインターから a[5] へのポインターを減算できます。a[8] は配列の最後の要素の 1 つ後ろにあるため、a[8] へのポインターから a[5] へのポインターを減算することもできます。(a[8] は配列にありません。a[7] は最後の要素です。) b[2] へのポインターから a[5] へのポインターを減算することはできません。 b[2] と同じ配列。または、より正確には、このような減算を行う場合、動作は未定義です。指定されていないのは結果だけではないことに注意してください。結果として無意味な数値が得られるとは期待できません。未定義です。C 標準によれば、これは、結果として何が起こるかについて C 標準が何も述べていないことを意味します。あなたのプログラムはあなたに妥当な答えを与えるか、中断するか、ファイルを削除する可能性があり、それらの結果はすべて C 標準に準拠しています。

許可された減算を行うと、結果は、2 番目に指定された要素から最初に指定された要素までの要素の数になります。したがって、a[5]-a[2]は 3 であり、a[2]-a[5]-3 です。これは、タイプに関係なく当てはまりますa。C 実装では、距離をバイト (または使用する単位) から適切な型の要素に変換する必要があります。aがそれぞれ 8 バイトの double の配列である場合a[5]-a[2]、3 要素の場合は 3 です。aがそれぞれ 1 バイトの char の配列である場合a[5]-a[2]、3 要素の場合は 3 です。

ポインターが単なる数値ではないのはなぜですか? 一部のコンピューター、特に古いコンピューターでは、メモリのアドレス指定がより複雑でした。初期のコンピューターのアドレス空間は小さかった。製造業者がより大きなアドレス空間を作りたいと思ったとき、古いソフトウェアとの互換性を維持したいとも考えていました。また、ハードウェアの制限により、メモリをアドレス指定するためのさまざまなスキームを実装する必要がありました。これらのスキームには、メモリとディスクの間でデータを移動したり、アドレスを物理メモリ位置に変換する方法を制御するプロセッサ内の特殊なレジスタを変更したりする必要があった可能性があります。そのようなマシンでポインターが機能するには、単純なアドレスだけでなく、より多くの情報が含まれている必要があります。このため、C 標準では、ポインターをアドレスとして定義するだけでなく、アドレスに対して演算を行うことができます。

最新のマシンでも、複雑な問題が発生する可能性があります。Digital の Alpha プロセッサでは、関数へのポインタに関数のアドレスが含まれていません。関数の記述子のアドレスです。その記述子には関数のアドレスが含まれており、関数を正しく呼び出すために必要な追加情報が含まれています。

などの関係演算子に関して>、C 標準では、6.5.8 パラグラフ 5 で、上記のように、減算できる同じポインターを比較することができ、ポインターを集約オブジェクトのメンバー (a構造体または共用体)。配列のメンバー (またはその終了アドレス) へのポインターは、予想どおりに比較されます。インデックスの大きい要素へのポインターは、インデックスの小さい要素へのポインターよりも大きくなります。同じ共用体の 2 つのメンバーへのポインターは等しいと比較されます。構造体の 2 つのメンバーへのポインターの場合、後で宣言されたメンバーへのポインターは、以前に宣言されたメンバーへのポインターよりも大きくなります。

上記の制約内にとどまる限り、ポインターはメモリアドレスである数値と考えることができます。

通常、C 実装が C 標準で必要な動作を提供するのは簡単です。コンピューターにベース アドレスとオフセットなどの複合ポインター スキームがある場合でも、通常、配列のすべての要素は互いに同じベース アドレスを使用し、構造体のすべての要素は互いに同じベース アドレスを使用します。そのため、コンパイラは単にポインタのオフセット部分を減算または比較して、目的の差異または比較を取得できます。

ただし、そのようなコンピューターで異なる配列へのポインターを減算すると、奇妙な結果が得られる可能性があります。ベース アドレスとオフセットによって形成されるビット パターンは、別のポインターがメモリ内の下位アドレスを指している場合でも、(単一の整数として解釈される場合) よりも大きく見える可能性があります。これが、C 標準によって設定されたルール内にとどまらなければならない理由の 1 つです。

于 2012-07-30T01:02:09.497 に答える
27

ポインター減算は、同じ型の 2 つのポインター間の配列要素の数を生成します。

例えば、

int buf[10] = /* initializer here */;

&buf[10] - &buf[0];  // yields 10, the difference is 10 elements

ポインタ比較。たとえば、>関係演算子の場合:左側のポイントされた配列要素または構造体メンバーが右側のポイントされた配列要素または構造体メンバーの後にある場合、>演算は生成され、そうでない場合は生成されます。配列と構造体は順序付けられたシーケンスであることを忘れないでください。10

 &buf[10] > &buf[0];  // 1, &buf[10] element is after &buf[0] element
于 2012-07-30T00:12:47.443 に答える
5

2 つのポインター アドレスを減算すると、その型の要素の数が返されます。

したがって、整数の配列とそれに 2 つのポインターがある場合、それらのポインターを減算すると、バイト数ではなく、その間の int 値の数が返されます。char 型と同じです。そのため、特にバイト バッファまたはワイド文字を使用している場合は、式が正しい値を計算していることに注意する必要があります。ストレージに 1 バイトを使用しないもの (int、short など) にバイトベースのバッファー オフセットが必要な場合は、最初にポインターを char* にキャストする必要があります。

于 2012-07-30T00:04:18.567 に答える
-1

最初の式は、あるポインターを別のポインターから減算します。これが役立つ理由の簡単な例として、C文字列について考えてみます。文字列は連続したメモリにあるため、文字列の最初の文字のアドレスと最後の文字のアドレスがある場合は、次のようにして文字列の長さを見つけることができます。

int strLength = (last_char_address - first_char_address) + 1;

このようなポインタ演算は型を認識します。つまり、演算の結果は、2つのポインタ間の特定の型の要素の数を表します。を使用した上記の例charでは、違いは文字数です。これは、たとえば2へのポインタに対しても同様に機能しますstructs

同様に、2番目の式は単純にポインタを比較しており、結果は1または0になります。非常に単純な例として、配列の要素5のアドレスは常に>要素4のアドレスです:&string[4] > &string[5]true。

于 2012-07-29T23:49:11.823 に答える
-1

ポインターは、多くの場合、0x0A31FCF20 (または 10 進数で 2736770848) や 0xCAFEDEAD (システムがこれをエラーを示すために使用することがありますが、詳細は覚えていません) など、メモリ アドレスを表す単なる数値と考えることができます。

ポインターの比較は、ポインターの配列をソートする際によく使用されます。ポインターの並べ替えられた配列は、ポインターがポインターのリストにあるかどうかを確認する必要がある場合に役立ちます。リストがソートされている場合、ポインターがそのリストにあるかどうかを判断するために、リストのすべての要素を調べる必要はありません。リストを並べ替えるには、比較を使用する必要があります。

ポインター演算は、データのチャンクへのポインターがあり、データのチャンクの先頭にないものにアクセスする必要がある場合によく使用されます。例えば:

const char *string = "hello world!"
const char *substring = string+6;
std::cout << string << "\n";
std::cout << substring << std::endl;

これは次のように出力されます。

hello world!
world!

ここでは、"hello world!" の最初の 6 文字の後の文字列、つまり"world!". std::string可能であれば、代わりに利用可能な場所を使用する必要があることに注意してください。ポインター演算に非常によく似た概念は、ランダム アクセス反復子です。

ポインターを減算すると、これら 2 つのポインター間の距離を見つけるのに役立ちます。配列の最初の要素へのポインターと、配列の最後の要素の 1 つ後の要素へのポインターがある場合、これら 2 つのポインターを減算すると、配列のサイズを見つけるのに役立ちます。

ポインターを整数として扱う可能性がある別のケースは、XOR リンク リストと呼ばれるリンク リストの最適化されたバージョンです。詳細については、こちらをご覧ください。必要に応じて、これを拡張できます。コメントで教えてください。

于 2012-07-30T00:09:58.880 に答える
-2

intアドレスは、多くの点で のように扱うことができます。唯一の違いは、intそのアドレスのサイズの数を表していることです。たとえば、たまたま(たとえば の安全な命令からint * p) の値を持っている場合、それはアドレス 234 を表します。現在は (この例では 4 バイトの int を想定) 238、つまり. 実際には と同等です。int と同じように比較などを行うことができます。コンテキストによっては、これは便利です。これにより、不必要に逆参照するようなことをする必要がなくなります。234p = new int[12];p += 1;pp[1]p[x]*(p+x)p[0]p[1]p = &p[1]

于 2012-07-30T00:21:13.277 に答える