8

C++でUTF-8を解析したいと思います。新しい文字を解析するとき、それがASCIIバイトなのかマルチバイト文字のリーダーなのか、また入力文字列が残りの文字を含めるのに十分な長さなのかどうかも事前にわかりません。

簡単にするために、次の4バイトa、、、およびに名前を付けたいと思います。また、C ++を使用しているため、参照を使用して名前を付けたいと思います。bcd

アクセスが安全であることがわかる前にそれらにアクセスしない限り、関数の最初にそれらの参照を定義することは有効ですか?例:

void parse_utf8_character(const string s) {
    for (size_t i = 0; i < s.size();) {
        const char &a = s[i];
        const char &b = s[i + 1];
        const char &c = s[i + 2];
        const char &d = s[i + 3];

        if (is_ascii(a)) {
            i += 1;
            do_something_only_with(a);
        } else if (is_twobyte_leader(a)) {
            i += 2;
            if (is_safe_to_access_b()) {
                do_something_only_with(a, b);
            }
        }
        ...
     }
}

上記の例は、私が意味的にやりたいことを示しています。なぜこれを実行したいのかはわかりませんが、明らかに実際のコードはより複雑になるため、アクセスが安全で必要であることがわかっている場合にのみb、c、dを定義するのは冗長すぎます。

4

3 に答える 3

5

これには3つの見方があります。

  • 正式
    には、誰が知っていますか。かなりの時間を使ってあなたのために見つけることができましたが、それならあなたもそうすることができました。または任意のリーダー。そして、それは非常に実用的ではありません。
    編集:OK、それを調べてください、あなたは私があなたのためにそれを調べずにフォーマルに言及することに満足していないようですので。正式には運が悪い:
    N3280(C ++ 11)§5.7/ 5 「ポインタオペランドと結果の両方が同じ配列オブジェクトの要素を指している場合、または配列オブジェクトの最後の要素を1つ過ぎている場合、評価オーバーフローを発生させてはならない。それ以外の場合、動作は定義されていません。」
    これにより望ましくない動作が発生する可能性がある2つの状況:(1)セグメントの終わりを超えてアドレスを計算する、および(2)デバッグチェックを有効にして、コンパイラがサイズを認識している配列を超えてアドレスを計算する。

  • 技術的
    には、左辺値から右辺値への変換を回避する限り、おそらく問題ありません。参照がポインターとして実装されている場合は、ポインターと同じくらい安全であり、コンパイラーがそれらをエイリアスとして実装することを選択した場合も、それはわかった。

  • 微妙なことに不必要に経済的
    に依存することはあなたの時間を浪費し、そしてまた他の人がコードを扱う時間を浪費します。だから、良い考えではありません。代わりに、それらが参照するものが存在することが保証されているときに名前を宣言します。

于 2012-12-09T22:54:04.823 に答える
4

アクセスできないメモリへの参照の合法性に入る前に、コードに別の問題があります。への呼び出しは、よりも大きいパラメーターで呼び出すs[i+x]可能性があります。C ++ 11標準では、 ([string.access]、§21.4.5)について次のように述べています。string::operator[]s.size()string::operator[]

必要なもの:pos <= size()。

戻り値:*(begin()+ pos)pos <size()の場合、それ以外の場合は値charT();のタイプTのオブジェクトへの参照。参照値は変更しないでください。

これは、呼び出しが未定義の動作であることs[x]を意味しx > s.size()ます。そのため、実装は、たとえばアサーションによって、プログラムを非常にうまく終了させることができます。

現在は継続的であることが保証されているためstring、&s [i]+xを使用してアドレスを取得することでこの問題を回避できます。実践では、これはおそらくうまくいくでしょう。

ただし、厳密に言えば、これを行うことは残念ながら依然として違法です。この理由は、標準では、ポインターが同じ配列内にあるか、配列の終わりを1つ超えている場合にのみ、ポインター演算が許可されるためです。(C ++ 11)標準の関連部分は、[expr.add]、§5.7.5にあります。

ポインタオペランドと結果の両方が同じ配列オブジェクトの要素を指している場合、または配列オブジェクトの最後の要素を1つ過ぎている場合、評価はオーバーフローを生成してはなりません。それ以外の場合、動作は定義されていません。

したがって、無効なメモリ位置への参照またはポインタを生成することは、ほとんどの実装で機能する可能性がありますが、ポインタを逆参照したり参照を使用したりしない場合でも、技術的には未定義の動作です。UBに依存することは、ほとんどの場合、良い考えではありません。なぜなら、UBがすべての対象システムで機能する場合でも、UBが将来も機能し続けるという保証はないからです。

于 2012-12-09T23:09:13.570 に答える
2

原則として、不正な可能性のあるメモリアドレスを参照するという考え自体は、完全に合法です。参照は内部のポインタにすぎず、間接参照が発生するまでポインタ演算は有効です。

編集:この主張は実際的なものであり、公表された基準でカバーされているものではありません。公開された標準には、正式には未定義の動作である多くのコーナーがありますが、実際には予期しない動作は発生しません。

たとえば、配列の終わりの後の2番目の項目へのポインターを計算する可能性を考えてみましょう(@DanielTrebbienが示唆しているように)。標準では、オーバーフローによって未定義の動作が発生する可能性があるとされています。実際には、オーバーフローは、配列の上端がポインタでアドレス指定可能なスペースに不足している場合にのみ発生します。ありそうもないシナリオ。それが起こったとしても、ほとんどのアーキテクチャで悪いことは何も起こりません。違反しているのは、ポインタの違いに関する特定の保証ですが、ここでは適用されません。

@JoSo文字配列を使用している場合は、コード内でconst-referencesをconst-pointersに置き換えることで、参照セマンティクスに関する不確実性の一部を回避できます。そうすれば、コンパイラが値をエイリアスしないことを確認できます。

于 2012-12-09T22:51:00.653 に答える