ポインタをインクリメントする理由


25

私は最近C ++の学習を始めましたが、ほとんどの人が(これまで読んでいたことによると)ポインターに苦労しています。

伝統的な意味ではなく、私はそれらが何であり、なぜ使用されているのか、どのように役立つのかを理解していますが、ポインタのインクリメントがどのように役立つかを理解することはできません、誰もポインタのインクリメントが便利な概念と慣用的なC ++?

この質問は、Bjarne StroustrupによるA Tour of C ++の本を読み始めた後に出てきたものです。 。


11
ポインターは単なる反復子です
チャールズサルビア14

1
読むべきでないものを読むコンピューターウイルスを作成するためのお気に入りのツールの1つです。また、アプリの脆弱性の最も一般的なケースの1つです(アプリが想定されている領域を超えてポインターをインクリメントし、それを読み書きする場合)> HeartBleedバグを参照してください。
サム14

1
@vasileこれは、ポインターの悪いところです。
ランチャー

4
C ++の長所と短所は、セグメンテーション違反を呼び出す前にもっと多くのことができるということです。通常、別のプロセスのメモリ、システムメモリ、または保護されたアプリメモリにアクセスしようとすると、セグメンテーション違反が発生します。通常のアプリケーションページ内のアクセスはすべてシステムによって許可されており、合理的な制限を強制するのはプログラマー/コンパイラー/言語に委ねられています。C ++を使用すると、ほとんど何でも好きなことができます。独自のメモリマネージャを持つopensslに関しては、そうではありません。デフォルトのC ++メモリアクセスメカニズムのみがあります。
サム14

1
@INdek:アクセスしようとしているメモリが保護されている場合にのみセグメンテーション違反が発生します。ほとんどのオペレーティングシステムはページレベルで保護を割り当てるため、通常はポインターが開始するページにあるものにアクセスできます。OSが4Kページサイズを使用している場合、それはかなりの量のデータです。ポインタがヒープ内のどこかで始まる場合、アクセスできるデータの量は誰でも推測できます。
TMN 14

回答:


46

配列がある場合、配列の要素を指すポインターを設定できます。

int a[10];
int *p = &a[0];

ここでは、pの最初の要素を指しaています、a[0]。これで、次の要素を指すようにポインターを増分できます。

p++;

次にp、2番目の要素を指しa[1]ます。ここで要素にアクセスできます*p。これは、整数のインデックス変数を使用して配列の要素にアクセスする必要があるJavaとは異なります。

ポインタが配列の要素を指していない C ++でのポインタのインクリメントは、未定義の動作です。


23
はい、C ++では、配列の境界外へのアクセスなどのプログラミングエラーを回避する必要があります。
グレッグヒューギル14

9
いいえ、配列要素以外を指すポインターをインクリメントすることは未定義の動作です。ただし、移植性の低い低レベルの処理を行う場合、通常、ポインターをインクリメントすることは、メモリ内の次の要素にアクセスすることです。
グレッグヒューギル14

4
配列である、または配列として扱うことができるものがいくつかあります。テキストの文字列は、実際には文字の配列です。場合によっては、long intはバイトの配列として扱われますが、これは簡単に問題を引き起こす可能性があります。
アマダノン株式会社14

6
これでタイプがわかりますが、動作は5.7加算演算子[expr.add]で説明されています。具体的には、5.7 / 5は、最後から1回を除いて配列の外に行くのはUBであると述べています。
役に立たない14

4
最後の段落は次のとおりです。ポインターオペランドと結果の両方が同じ配列オブジェクトの要素を指す場合、評価はオーバーフローを生成しません。それ以外の場合、動作は未定義です。そのため、結果が配列にも末尾にも貼り付けられていない場合、UBが得られます。
役に立たない14

37

ポインタのセマンティクスが(アレクサンダー・ステパノフさんのオフに基づいてC ++標準ライブラリの背後にある設計哲学の基本的な側面を反映するので、ポインタをインクリメントすること、慣用C ++でSTLを

ここで重要な概念は、STLがコンテナ、アルゴリズム、およびイテレータを中心に設計されていることです。ポインタは単なる反復子です。

もちろん、ポインタをインクリメント(または加算/減算)する機能はCに戻ります。多くのC文字列操作アルゴリズムは、ポインタ演算を使用して簡単に記述できます。次のコードを検討してください。

char string1[4] = "abc";
char string2[4];
char* src = string1;
char* dest = string2;
while ((*dest++ = *src++));

このコードは、ポインター演算を使用して、ヌル終了Cストリングをコピーします。ループは、ヌルが検出されると自動的に終了します。

C ++では、ポインターのセマンティクスはイテレーターの概念に一般化されます。ほとんどの標準C ++コンテナには、イテレータが用意されています。イテレータには、beginおよびendメンバー関数を介してアクセスできます。イテレータは、インクリメント、デリファレンス、デクリメントまたはアドバンスされることがあるという点で、ポインタのように動作します。

を反復処理するにはstd::string、次のように言います。

std::string s = "abcdef";
std::string::iterator it = s.begin();
for (; it != s.end(); ++it) std::cout << *it;

プレーンなC文字列へのポインタをインクリメントするのと同じように、イテレータをインクリメントします。このコンセプトが強力な理由は、テンプレートを使用して、必要なコンセプト要件を満たすあらゆるタイプのイテレータで機能する関数を作成できるためです。そして、これがSTLの力です。

std::string s1 = "abcdef";
std::vector<char> buf;
std::copy(s1.begin(), s1.end(), std::back_inserter(buf));

このコードは、文字列をベクターにコピーします。このcopy関数は、インクリメントをサポートするイテレータ(プレーンポインタを含む)で動作するテンプレートです。copyプレーンなC文字列で同じ関数を使用できます。

   const char* s1 = "abcdef";
   std::vector<char> buf;
   std::copy(s1, s1 + std::strlen(s1), std::back_inserter(buf));

私たちは、使用することができますcopystd::mapstd::setまたは任意のカスタム・コンテナサポート反復子います。

ポインタは特定の種類のイテレータであることに注意してください:ランダムアクセスイテレータは、+and -演算子によるインクリメント、デクリメント、および前進をサポートすることを意味します。他のイテレータタイプは、ポインタセマンティクスのサブセットのみをサポートします。双方向イテレータは、少なくともインクリメントとデクリメントをサポートします。前方イテレータ支持体は、少なくともインクリメント。(すべてのイテレータタイプは逆参照をサポートしています。)このcopy関数には、少なくともインクリメントをサポートするイテレータが必要です。

イテレータのさまざまな概念については、こちらをご覧ください

したがって、ポインタの増分は、C配列を反復処理する、またはC配列の要素/オフセットにアクセスするための慣用的なC ++の方法です。


3
最初の例のようにポインターを使用しますが、イテレーターとは考えていませんでしたが、今では非常に理にかなっています。
dyesdyes 14

1
「nullに遭遇すると、ループは自動的に終了します。」これは恐ろしいイディオムです。
チャールズウッド

9
@CharlesWood、その後、私はあなたがCはかなり恐ろしい見つけなければならないと思います
サイラー

7
@CharlesWood:もう1つの方法は、文字列の長さをループ制御変数として使用することです。つまり、文字列を2回走査します(一度長さを決定し、1回文字をコピーします)。1MHzのPDP-7で実行している場合、それが実際に増え始める可能性があります。
TMN 14

3
@INdek:まず第一に、CとC ++は破壊的な変更を導入するためにあらゆるコストを回避しようとします-そして、文字列リテラルのデフォルトの動作を変更することはかなりの修正だと思います。しかし、最も重要なことは、ゼロで終わる文字列は単なる慣習である(文字列リテラルはデフォルトでゼロで終わるという事実と、ライブラリ関数がそれらを期待するという事実を簡単に理解できるようにする)いくつかのCライブラリはそれらを使用します(たとえば、OLEのBSTRを参照)。
マッテオイタリア14

16

ポインター演算はCにあったため、C ++にあります。ポインター演算は、アセンブラーの通常のイディオムであるため、Cにあります。

「増分レジスタ」が「定数値1をロードしてレジスタに追加する」よりも速いシステムがたくさんあります。さらに、1つの命令で「レジスタBで指定されたアドレスからDWORDをAにロードしてから、sizeof(DWORD)をBに追加する」ことができるシステムがかなりあります。最近では、最適化コンパイラがこれを整理してくれると期待しているかもしれませんが、1973年にはこれは実際には選択肢ではありませんでした。

これは、C配列が境界チェックされておらず、C文字列にサイズが埋め込まれていないという基本的に同じ理由です。言語は、すべてのバイトとすべての命令がカウントされるシステムで開発されました。

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