26

最近、次のようなポインターを比較するコードをいくつか書きました。

if(p1+len < p2)

しかし、一部のスタッフは、次のように書くべきだと言いました。

if(p2-p1 > len)

安全であるために。ここで、p1p2char *ポインタで、lenは整数です。私はそれについて何も知りません。そうですか?

EDIT1: もちろん、p1p2は同じメモリ オブジェクトへのポインターです。

EDIT2: ちょうど 1 分前に、コード (約 3K 行) でこの質問のボゴを見つけました。これは、4 バイトのポインターに格納できないlenほど大きいため、 p1+len < p2trueです。実際にはそうではないので、状況によっては次のようなポインターを比較する必要があると思います。p1+len

if(p2 < p1 || (uint32_t)p2-p1 > (uint32_t)len)
4

6 に答える 6

25

一般に、ポインターを安全に比較できるのは、ポインターが両方とも同じメモリ オブジェクトの一部 (またはオブジェクトの末尾の 1 つ後ろ) を指している場合のみです。p1p1 + len、およびp2すべてがこの規則に準拠している場合、両方の-testifは同等であるため、心配する必要はありません。一方、p1p2がこの規則に準拠していることがわかってp1 + lenいて、終わりを過ぎている可能性がある場合にのみif(p2-p1 > len)安全です。(しかし、あなたの場合はそうではないと思います。これは、p1メモリブロックの先頭をp1 + len指し、その終了後の位置を指していると思いますよね?)

彼らが考えていたのは整数演算です。オーバーフローする可能性があるが、オーバーフローしないi1 + i2ことがわかっている場合、ラップアラウンドするか (符号なし整数の場合)、未定義の動作をトリガーする可能性があります (符号付き整数の場合)。または両方(システムが符号付き整数オーバーフローのラップアラウンドを実行する場合)、その問題はありません。i3 - i1i1 + i2 < i3i3 - i1 > i2


追加するために編集:コメントに、「lenはバフからの値なので、何でもかまいません」と書きます。その場合、有効ではない可能性があるため、それらはまったく正しく、p2 - p1 > lenより安全です。p1 + len

于 2013-07-11T03:16:13.467 に答える
12

「未定義の動作」がここに適用されます。2 つのポインターは、両方が同じオブジェクトまたはそのオブジェクトの末尾の後の最初の要素を指していない限り、比較できません。次に例を示します。

void func(int len)
{
    char array[10];
    char *p = &array[0], *q = &array[10];
    if (p + len <= q)
        puts("OK");
}

この関数について、次のように考えることができます。

// if (p + len <= q)
// if (array + 0 + len <= array + 10)
// if (0 + len <= 10)
// if (len <= 10)
void func(int len)
{
    if (len <= 10)
        puts("OK");
}

ただし、コンパイラは、 のすべての有効なptr <= qが true であることを認識しているため、関数を次のように最適化する可能性があります。ptr

void func(int len)
{
    puts("OK");
}

はるかに高速!しかし、あなたが意図したものではありません。

はい、これを行うコンパイラが実際に存在します。

結論

これが唯一の安全なバージョンです。ポインターを減算して結果を比較し、ポインターを比較しないでください。

if (p - q <= 10)
于 2013-07-11T03:31:55.357 に答える
9

技術的には、同じ配列へのポインタp1でなければなりません。p2それらが同じ配列にない場合、動作は未定義です。

追加バージョンの場合、 の型はlen任意の整数型にすることができます。

差分バージョンの場合、減算の結果は になりますがptrdiff_t、任意の整数型は適切に変換されます。

これらの制約内では、どちらの方法でもコードを記述できます。どちらもより正確ではありません。部分的には、解決している問題によって異なります。問題が「配列のこれら 2 つの要素が要素以上len離れているか」である場合は、減算が適切です。質問が「(aka )p2と同じ要素である」である場合、追加は適切です。p1[len]p1 + len

実際には、均一なアドレス空間を持つ多くのマシンでは、異なる配列へのポインターを差し引くことで問題を解決できますが、おかしな結果が得られる可能性があります。たとえば、ポインターが何らかの構造体型へのポインターであるが、同じ配列の一部ではない場合、バイト アドレスとして扱われるポインター間の差は、構造体サイズの倍数ではない可能性があります。これにより、特有の問題が発生する可能性があります。それらが同じ配列へのポインターである場合、そのような問題は発生しません。そのため、制限が設けられています。

于 2013-07-11T03:17:58.363 に答える
0

ディートリッヒがすでに述べたように、無関係なポインターを比較することは危険であり、未定義の動作と見なすことができます。

2 つのポインターが (32 ビット Windows システムで) 0 から 2GB の範囲内にある場合、2 つのポインターを減算すると、-2^31 から +2^31 の間の値が得られます。これは、まさに符号付き 32 ビット整数のドメインです。したがって、この場合、結果は常に予想されるドメイン内にあるため、2 つのポインターを減算することは理にかなっているように思われます。

ただし、LargeAddressAware フラグが実行可能ファイルで有効になっている場合 (これは Windows 固有のものであり、Unix については不明です)、アプリケーションには 3GB のアドレス空間があります (/3G フラグを使用して 32 ビット Windows で実行した場合)。または 4GB (64 ビット Windows システムで実行する場合)。その後、2 つのポインターの減算を開始すると、結果が 32 ビット整数のドメイン外になる可能性があり、比較は失敗します。

これが、元々アドレス空間が 2GB の 2 つの等しい部分に分割されていた理由の 1 つであり、LargeAddressAware フラグがまだオプションである理由の 1 つだと思います。しかし、私の印象では、現在のソフトウェア (あなた自身のソフトウェアとあなたが使用している DLL) は非常に安全であるように思われ (もう誰もポインターを減算しませんよね?)、私のアプリケーションでは LargeAddressAware フラグがデフォルトでオンになっています。

于 2013-07-11T08:09:18.687 に答える