反復子(最初の要素の前の1つ)と未定義の振る舞いを逆転させますか?

ポインタを扱うとき、最後のポインタを指すポインタは(ほとんど)定義された動作を持ちます:

int array[8];
int* x = array + 8;//Still considered pointing to part of array in expressions

反復子は、イテレータのポインタを最後のものと比較することによって、これを利用します。

逆イテレータは同じことを行いますが、配列の開始前には同じものを行います。

int array[8];
int* x = array - 1;

これは未定義の動作ではありませんか?コンテナには余分な要素があり、開始時に [1] とみなされますか?

1

3 答え

逆イテレータは実際にイテレータではありません。彼らはもっと「イテレーターアダプター」に似ています。実際のイテレータを返すために、逆イテレータの base()メンバ関数を呼び出すことができます。これにより、-1オフセットが発生するので、 rbegin()。base()= = end() rend()。base()== begin()したがって、明示的な「開始前に」ポインタ値などの必要はありません。 (もちろん forward_list は例外です)。

1
追加された
@Pubby:標準ライブラリはコンパイラの一部です。標準ライブラリの作者は、コンパイラがどのように動作するか(アプリケーション開発者が作成できないという前提)を前提にしています。
追加された 著者 Martin York,
@curiousguy:それは正しいです。しかし、完全に無関係です。
追加された 著者 Martin York,
追加された 著者 Martin York,
@curiousguy:反復子は不透明なオブジェクトです。逆イテレータは、デクリメント可能なイテレータの周りのラッパーです。
追加された 著者 Kerrek SB,
@curiousguy:あなたの2番目のコメントについて(「反復子は否定的に定義されています」):あなたが冗談を言っているかどうかは分かりませんが、不透明であってもあなたが内部を知ることを妨げるわけではありません。単にあなたがそれらを知る を持っていないことを意味します。イテレーターについて知っておく必要があることは、イテレーターをインクリメントできることと、イテレーターを iterator_traits に差し込むことで情報を取得できることだけです。対照的に、逆方向反復子は、その内部の点で「透過的に」定義されています。
追加された 著者 Kerrek SB,
@Pubby: begin()をリバースイテレータに変換すると、ベースが begin()である逆イテレータが返されます。イテレータを間接参照することは無効です。これには2つの理由で無効な *(base() - 1)へのアクセスが含まれます。
追加された 著者 Mike Seymour,
"リバースイテレータは実際にイテレータではありません。"実際のイテレータは何ですか?
追加された 著者 curiousguy,
@KerrekSB "反復子は不透明なオブジェクトです" "なので、ノードポインタにパブリックアクセスするリストイテレータはイテレータではありませんか?ですから、反復子の概念は否定と定義されていますか?
追加された 著者 curiousguy,
@LokiAstari "のモデル:ランダムアクセスイテレータ" QED
追加された 著者 curiousguy,
@LokiAstari "それは正しいですが、完全に無関係です。"何が正当で無関係か?
追加された 著者 curiousguy,
@Pubby "これは理にかなっていますが、動作はまだ定義されていますか?"はい。 "変換中に無効なポインタ演算が起こっているようですが"これは起こりません。
追加された 著者 curiousguy,
@LokiAstari "標準ライブラリはコンパイラの一部であるため、標準ライブラリの作者は、コンパイラがどのように動作するか(アプリケーション開発者が作成できないという前提)を前提にしています。"それは正しい。しかし、完全に無関係です。
追加された 著者 curiousguy,
@Pubby "しかし、これはすべてのユーザ定義演算子にUBがあることを意味しないでしょうか?"いいえ、Lokiはここで間違っています。逆イテレータアダプタは、無効な算術演算が行われないように設計されています。価格は、1)非効率的である。 2)それは奇妙な無効化の動作です
追加された 著者 curiousguy,
@MikeSeymour " iteratorを参照解除するのは無効です"はい、予期しないことではありません: reverse(begin()) rend()それは1つの終わり(逆)イテレータです。
追加された 著者 curiousguy,
@ケレスクSB私はまったく冗談を言っていません、あなたがいるかと思います。反復子は、自分ができること(肯定的な定義)によって定義され、実行できないもの(否定的な定義)によって定義されません。 "が不透明であっても、あなたが内部を知ることを妨げるわけではありません" base を公開しても、逆イテレータはイテレータではありません。対照的に、逆方向反復子は、その内部の点で「透過的に」定義されています。 "何と対照的に?とにかく、リンクされたドキュメントは、逆のイテレータイテレータであることを明確に示しています。
追加された 著者 curiousguy,
これは理にかなっていますが、まだ定義されている動作ですか?変換中に何らかの無効なポインタ演算が起こっているようです。
追加された 著者 Pubby,
@LokiAstari私はそれを理解していますが、これはすべてのユーザ定義イテレータにUBがあることを意味しませんか?
追加された 著者 Pubby,
@curiosguyあなたはそれが本当ですか? begin()を逆方向反復子に変換するのは無効な算術演算を伴うようです。
追加された 著者 Pubby,
それは愚かなハックですが、今、私はそれについて考えることは、理にかなっています。
追加された 著者 Pubby,

標準[24.4.1/1]によると、逆イテレータとベースとの関係は次のようになります。

&*(reverse_iterator(i)) == &*(i - 1)

したがって、配列の開始前にはパディングはありません。そして、上記の方法は、標準に従って動作が定義されています。

0
追加された
@curiousguyよくやった..それは確かにC + +の標準からのいくつかのテキストです - 多少非sequitur
追加された 著者 Anthony Blake,
負のインデックスは許されます:&a [i]は(a + i)と評価されるアドレス計算であるため、&a [-i]は(a - i)として評価されます。 int i [8]、int * p = i + 4の場合、p [-2]は完璧です。ポインタの仕組みしかし、配列の範囲外のメモリへのアクセスは未定義です。
追加された 著者 Anthony Blake,
@curiousguyあなたはちょうど "リバースイテレータの基本的なアイデアは&*(reverse_iterator(i))==&*(i - 1)" 30分前と言わなければなりません。
追加された 著者 Anthony Blake,
@curiousguyはい、ベースを調整しないリバースイテレータの場合、すべてのコンパイラが意図しない振る舞いをすることを証明できますか(つまり、マシンで検証可能な証明を提供できますか?
追加された 著者 Anthony Blake,
@curiousguyあるいは単に例を提供するだけです - 正式な証明=を心配しないでください)意図しないの振る舞いを脇に置くと、コンパイラが実際には逆の反復子でエラーのあるコードを生成するかどうか不思議です配列の基底の前の要素を指します。
追加された 著者 Anthony Blake,
@curiousguy彼らが誤りのあるコードを生成するコンパイラでない限り、私たちは定義された動作を定量的に言うことができます。
追加された 著者 Anthony Blake,
@curiousguyあなたが言っていることは、間違った動作の例や校正を見つけることができないということで、 定量的に定義されている動作ですか?
追加された 著者 Anthony Blake,
@curiousguy "非常に簡単です:チェックされたイテレータを使用してください。"どのようなコンパイラーですか? 1つは作った?
追加された 著者 Anthony Blake,
@curiousguy何が間違った動作をしたのですか?
追加された 著者 Anthony Blake,
@curiousguy私はここで全く真面目です:あなたは逆の反復子を使用すると、間違った動作の現実世界の例を教えてくれますか?またはあなたはできませんか?
追加された 著者 Anthony Blake,
@curiousguyチェックイテレータの詳細を尋ねるのではありません。チェックイテレータの存在と使用を正当化する不正な動作の実際の例を求めています
追加された 著者 Anthony Blake,
@curiousguyはい - しかし、チェックされたイテレータを使用しないと 間違った動作になりますか?つまり、チェックされたイテレータが実際に実際の障害を防ぐのですか?それとも、何の理由もなくコンパイルを止めているのでしょうか?チェックされたイテレータは、私たちが特定できない動作を防ぐために存在しますが、間違った動作を特定できる 可能な例を提供できるかどうかを尋ねています。私が言っていることは、非常に単純です:未定義の動作は、逆の反復子で不正な動作が起こることを証明しません。
追加された 著者 Anthony Blake,
@curiousguy別の方法で試してみましょう。未定義の動作とは、単に正確さを証明できないことを意味しますが、間違っているとは限りません。
追加された 著者 Anthony Blake,
@curiousguy私は、チェックされたイテレータが悪いことを否定していません!私は、この場合、逆の反復子であると言っています。実際には間違った振る舞いが起きる可能性は全くないということは、まったくもっともらしいことです。私はバグを防ぐことが悪いと言っているわけではありません、私は単に質問しています..私が言うように、未定義の振る舞いは間違った振る舞いではありません。この場合、動作が常に正しいことを証明するために使用し、プログラマの自由度を高めることができます。
追加された 著者 Anthony Blake,
@curiousguyどのCPUですか? x86またはARMではありません。
追加された 著者 Anthony Blake,
面白いですが、ポインタとの符号付き比較をすると問題はありません(折り返しが負です)。もちろん、署名付きの比較では他の問題が発生しますが、48ビットのアドレッシングなどを使用すると回避できます。
追加された 著者 Anthony Blake,
@curiousguy私は、あなたがメモリにアクセスしようとするx86例外のみを認識していました。アドレスをロードするときにどのような例外が発生する可能性がありますか?
追加された 著者 Anthony Blake,
@curiousguyそれはあなたに話していいですね。チェックされたイテレーターなどを強制するのが最良だと私は同意しますが、他の可能性について考えるのは面白かったです。私が言ったように、いくつかの条件付きで逆の反復子の正しさを証明することは可能かもしれませんが、それは多大な労力となり、おそらく実用的ではありません。
追加された 著者 Anthony Blake,
@curiousguyそのトラップを使って、ポインタが折り返しているかどうかを判断することができます。したがって、トラップを処理することは、実際には、逆イテレータがセグメントのベースで始まる配列の状況を処理するのに役立ちます
追加された 著者 Anthony Blake,
@curiousguy "はトラップを扱うのが複雑です。私はそれをユーザーモード(リング3)で行うことはできないと思うし、通常のポインタ算術と比べても非常にコストがかかります。"私はそれは非常にまれな。しかし、トラップがユーザーモードで処理できない場合は、おそらくそれを排除します。
追加された 著者 Anthony Blake,
@curiousguyあなたは、実際にメモリにアクセスする命令にコンパイルされている負のインデックスを持つポインタ算術の例を提供できますか?これは、コンパイラが実際にポインタを逆参照するように作られているように思えます。したがって、私は標準でそれを調べるつもりはありません。
追加された 著者 Anthony Blake,
@curiousguyあなたの議論を支持するいくつかの引用。ほかに何か?
追加された 著者 Anthony Blake,
@curiousguyメモリがアクセスされない場合、なぜ動作は未定義ですか?
追加された 著者 Anthony Blake,
@pubbyそれはcuriousguyの議論よりもずっと狂ったように聞こえる。しかし、なぜあなたは「この未定義の行動ではないのですか?
追加された 著者 Anthony Blake,
@curiousguyあなたはいくつかの引用を提供できますか?私はどこで標準を見なければならないのですか?
追加された 著者 Anthony Blake,
@curiousguy Uhh、私はそれを疑う - 実際に逆参照を行うコンパイラを見せてください。私が言ったように、それは作り上げられたと思う。
追加された 著者 Anthony Blake,
"実際にメモリにアクセスする命令にコンパイルされた負のインデックスを持つポインタ算術の例を提供できますか?"メモリにアクセスするとは言いませんでした。 " ISO規格で作成されているように、コンパイラがポインタを逆参照する
追加された 著者 curiousguy,
@AnthonyBlake "そのトラップを使用して、ポインタが折り返しているかどうかを判断することができますが、トラップを扱うのは複雑です。私はそれがユーザーモード(リング3)ではできないと思う。また、通常のポインタ演算に比べて非常にコストがかかります。
追加された 著者 curiousguy,
@Pubby sgi.com/tech/stl/ReverseIterator.html#3 "変数rfirstは、reverse_iterator <...> rfirst(V.end());で初期化されますが、逆参照されるときの値は*(V.end() - 1)です。反復イテレータの基本的なアイデンティティーは&*(reverse_iterator(i))==&*(i - 1)です。
追加された 著者 curiousguy,
@AnthonyBlakeセグメント化されたアーキテクチャでは、farポインタモードでは、ポインタはセグメントとオフセットで構成されます。したがって、(論理)アドレスレジスタは実際にセグメントレジスタとオフセット値を保持する実際のレジスタで構成されます。無効なセグメント値をロードすると、Intelプロセッサーのトラップが発生します。
追加された 著者 curiousguy,
@AnthonyBlake 8086では多くのメモリモードがサポートされています。セグメントモードでは、オフセットは16ビットの符号なしの数値です。もちろん、このモードは汎用コンピュータでは使用されません(ただし、専用ハードウェアや古いハードウェアで使用できます)。とにかく、ポインタ演算は、これらのセグメント化された操作モードで互換性があり(効率的に実装可能でなければならなかった)
追加された 著者 curiousguy,
"デリファレンスを実際に実行するコンパイラを表示する"あなたは混乱するタイプの要件です。 あなたはコンパイラではなく、参照を行っています。正しいC ++プログラムを書く必要がありますが、コンパイラではありません。 コンパイラは正しいC ++プログラムのための正しいコードを生成する必要があります。これは正しいC ++プログラムではないので、コンパイラの要件はありません。 IOW、あなたはUB を持っています。
追加された 著者 curiousguy,
&a [i] &*(a + i)です。 間接参照を行います。 "あなたがint i [8]、int * p = i + 4の場合、p [-2]はまったく問題ありません。
追加された 著者 curiousguy,
その行動が定義されていないことはあなたにとって重要ではありませんか?
追加された 著者 curiousguy,
@AnthonyBlake "コンパイラが実際に、配列の基底の前にある要素を指す逆方向の反復子を持つ不正なコードを生成するのではないかと不思議です。"コンパイラのドキュメントを引用できるかどうか不思議ですそのような使用が許可されていると言います。
追加された 著者 curiousguy,
1.4実装の準拠[intro.compliance]/2この規格はC ++実装の要件のみを述べていますが、それらの要件は、プログラム、プログラムの一部、またはプログラムの実行要件として表現されていれば、そのような要件は、以下の意味を有する。 - プログラムがこの国際標準の規則に違反していない場合、適合する実装は、そのリソースの制限内で、そのプログラムを受け入れ、正しく実行するものとする。
追加された 著者 curiousguy,
@AnthonyBlake C ++標準の内容を説明するテキストです。ここでは、その範囲について混乱している人がいるので、私はこれらを引用しています。
追加された 著者 curiousguy,
@AnthonyBlake N3242より:1.1スコープ[intro.scope]この国際標準は、C ++プログラミング言語の実装のための要件を規定しています。最初の要件は、それらが言語を実装することであり、したがってこの国際標準はC ++も定義しています。
追加された 著者 curiousguy,
@AnthonyBlakeはい、しかし、私はあなたの C ++標準の範囲と意味について説明していました。また、LokiはSGI STL 57分前のリンクを提供しました。
追加された 著者 curiousguy,
@AnthonyBlake N3242:未定義の振舞い "この国際規格が要求しない動作" [注:この国際標準が動作の明示的な定義を省略した場合、またはプログラムが誤った構造または間違ったデータです」。
追加された 著者 curiousguy,
@AnthonyBlake合計、絶対、ナンセンス。
追加された 著者 curiousguy,
@AnthonyBlakeは、どのステートメントをサポートするための引用をしていますか?
追加された 著者 curiousguy,
@AnthonyBlake "しかし、ベースを調整しないリバースイテレータの場合、コンパイラが意図しない振る舞いをすることを証明する(つまり、マシンで検証可能な証明を提供する)ことができますか?"非常に簡単です:チェックイテレータ
追加された 著者 curiousguy,
@AnthonyBlake "あなたが言っているのは、誤った行動の例や証拠を見つけることができないということです。そのため、行動は定量的に定義されていますか?あなたは私の時間をここで無駄にしています。
追加された 著者 curiousguy,
"ポインタが間接参照され、このポインタがオブジェクトを指していないため、メモリがアクセスされない場合、なぜ動作は未定義ですか?" * p の動作の定義は、 p = &a の場合、 a
追加された 著者 curiousguy,
"いくつかの引用を提供できますか"何の引用?
追加された 著者 curiousguy,
@AnthonyBlake "チェックイテレータ"について教えてください。
追加された 著者 curiousguy,
あなたは、標準が要求することによって非常に混乱しているようです。 「混乱している種類の要件です。(...)」というコメントをもう一度お読みください。
追加された 著者 curiousguy,
@AnthonyBlake "どのCPU?" x86
追加された 著者 curiousguy,
たとえば、@AnthonyBlake:アドレスレジスタに無効なアドレス値をロードすると、一部のCPUで例外が発生する可能性があります。
追加された 著者 curiousguy,
「あなたは間接参照をしていません。」「もちろん、私は逆参照をしています。 * p を計算するときにやっていることは? "は<と>の前に評価され、2段階に評価されます:"私だけでなく、言語の定義も同じように考える " "その効果は前の操作のステップ2を省略することです" "何かを省略する"?それは意味をなさない。
追加された 著者 curiousguy,
@AnthonyBlake "チェックイテレータの存在と使用を正当化する"、自分自身を教育する。チェックされたイテレータは、バグを検出したいという欲求によって正当化されます。
追加された 著者 curiousguy,
@AnthonyBlake "逆イテレータを使用したときの不正な動作の実際の例を教えてください。"チェックされたイテレータは、ベースポインタを適切に調整しない壊れた逆イテレータで使用すると中止されます。
追加された 著者 curiousguy,
参照のための標準からの引用: "同じ型のpとqの2つのポインタが、同じオブジェクトまたは同じ配列の要素または異なる関数の要素ではない異なるオブジェクトを指す場合、またはそれらのうちの1つだけがp q、p <= q、およびp> = qの結果は不特定である。 "" ==(等しい)および!=(等しくないto)演算子は、より低い優先順位と真理値の結果を除いて、関係演算子と同じ意味制約、変換、および結果タイプを持ちます。 "
追加された 著者 Pubby,
@AnthonyBlake逆のイテレータがルールを破るように定義されているかどうかを知りたかったのです。
追加された 著者 Pubby,
ポインタ間の比較は同じオブジェクトを指している必要があります。 a-1 == p ここでpは a の要素を指します。
追加された 著者 Pubby,
しかし、それだけです、あなたは逆参照を行う ことはありません。逆参照は実行時に発生し、コンパイル時に式の解析が行われます。したがって、 a [b] &()の前に評価され、それは2つのステップ、すなわち1)a + bを計算し、2)結果。次の操作は&()であり、そのステップは逆参照であるため、前の操作のステップ2を省略するです。あるいは、デリファレンスされた値がアセンブリを生成するときに消えてしまう(postfix ++ のように)未使用になるので、それを考えることができます。いずれにしても、実行時に逆参照はありません。
追加された 著者 Mike DeSimone,
p a にはないので a-1 == p false k は0と N-1 の間の整数です。 k /code>は a の要素数です。これは配列の定義から始まります。
追加された 著者 Mike DeSimone,

あなたがどちらかの端を越えてポインタを逆参照しない限り、それは未定義ではありません。

[0] is the start, not [1]. [-1] is one before [0].

The &a[b] syntax is treated by the compiler as exactly the same thing as a + b.

0
追加された
とにかく: int a [2]; 存在しない配列要素のアドレスを計算しようとしているため、は不正です。あなたが計算できる唯一の "擬似要素アドレス"は、最後の最後のものです。ここでは a + 2 です。
追加された 著者 curiousguy,
サブスクリプションは参照解除操作です a [i] *(a + i)と同じです。
追加された 著者 curiousguy,
array [8] ではありません&array [0] + 8
追加された 著者 Cory Nelson,
私の指摘していることは、を適用するとエスケープ文節が現れ、間接参照操作を [] 省略した結果をポインタ演算に変換します。私は標準のコピーを所有していない、または私はあなたのためにそれを探します。繰り返しますが、あなたは正しかったのですが、ポインタは正当なランダムイテレータではありませんでした。
追加された 著者 Mike DeSimone,