非constへのポインターと同じアドレスのconst引数へのポインターを使用した関数呼び出し


14

データの配列を入力し、ポインターを使用して別のデータの配列を出力する関数を書きたいのですが。

コンパイラがconstを最適化できることを知っているので、両方が同じアドレスsrcdst指している場合、結果はどうなのかと思います。未定義の動作ですか?(CとC ++の両方にタグを付けました。これは、答えが異なるかどうかわからないためです。両方について知りたいのです。)

void f(const char *src, char *dst) {
    dst[2] = src[0];
    dst[1] = src[1];
    dst[0] = src[2];
}

int main() {
    char s[] = "123";
    f(s,s);
    printf("%s\n", s);
    return 0;
}

上記の質問に加えてconst、元のコードでを削除すると、これは明確になりますか?

回答:


17

それは行動が明確に定義されていることは事実ですが- ありませんコンパイラは意味で「constのための最適化は、」あなたが意味することができていることというのは本当。

つまり、コンパイラーは、パラメーターがであるという理由だけで、が指すメモリーが別のポインターを通じて変更されないことを前提として許可const T* ptrされてptrいません。ポインタは等しくなくてもかまいません。constそのポインタを通じて変更を行うことはないあなたによって義務(=機能) -義務ではなく、保証されています。

実際にその保証を得るには、ポインタをrestrictキーワードでマークする必要があります。したがって、次の2つの関数をコンパイルすると、

int foo(const int* x, int* y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

int bar(const int* x, int* restrict y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

foo()この関数は二回から読まなければならないx一方で、bar()一度だけそれを読む必要があります:

foo:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, DWORD PTR [rdi]  # second read
        ret
bar:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, eax              # no second read
        ret

このライブをご覧くださいGodBolt

restrictCの唯一のキーワードです(C99以降)。残念ながら、これはこれまでC ++に導入されていません(C ++に導入するのがより複雑であるという悪い理由から)。ただし、多くのコンパイラは、それをのようにサポートしてい__restrictます。

結論:コンパイラは、コンパイル時に「難解な」ユースケースをサポートする必要f()があり、問題は発生しません。


の使用例については、この投稿を参照してくださいrestrict


const「そのポインタを介して変更を行わないこと(つまり機能)による義務」ではありません。C標準では、関数がconstキャストを介して削除し、その結果を介してオブジェクトを変更することを許可しています。本質的にconstは、プログラマーがオブジェクトを誤って変更しないようにするための助言であり便利です。
Eric Postpischil

@EricPostpischil:それはあなたが抜け出すことができる義務です。
einpoklum

あなたが得ることができる義務は義務ではありません。
Eric Postpischil

2
@EricPostpischil:1.ここでヘアを分割しています。2.それは真実ではありません。
einpoklum

1
これが理由でmemcpyありstrcpyrestrict引数で宣言されますが、引数でmemmoveはありません-後者のみがメモリブロック間のオーバーラップを許可します。
Barmar

5

これは明確に定義されており(C ++ではCではわかりません)、const修飾子があってもなくてもかまいません。

最初に探すことは、厳密なエイリアシング規則1です。同じオブジェクトsrcdst指す場合:

  • Cでは、互換性のある型でなければなりません。char*char const*互換性がありません。
  • C ++では、それらは同様のタイプでなければなりません。char*char const*似ています。

const修飾子に関してはdst == src、関数がsrcポイントするものを効果的に変更するとき、srcとして修飾すべきではないと主張するかもしれませんconst。これは機能しませんconst。次の2つのケースを検討する必要があります。

  1. のようにオブジェクトがconstとして定義されている場合、char const data[42];それを(直接的または間接的に)変更すると、未定義の動作が発生します。
  2. constオブジェクトへの参照またはポインタchar const* pdata = data;がのように定義されている場合、const2として定義されていない限り(1を参照)、基礎となるオブジェクトを変更できます。したがって、以下は明確に定義されています。
int main()
{
    int result = 42;
    int const* presult = &result;
    *const_cast<int*>(presult) = 0;
    return *presult; // 0
}

1) 厳密なエイリアスルールとは何ですか?
2) ですconst_cast安全ですか?


多分、OPは割り当ての再配列の可能性を意味しますか?
イゴールR.

char*char const*互換性がありません。_Generic((char *) 0, const char *: 1, default: 0))ゼロと評価されます。
Eric Postpischil

constオブジェクトへの参照またはポインタが定義されている場合」という表現は正しくありません。const修飾されたへの参照またはポインタが定義されている場合、それが指すように設定されているオブジェクトが(さまざまな方法で)変更されない可能性があることを意味します。(ポインターがconstオブジェクトを指している場合、それはオブジェクトが実際constに定義されていることを意味するため、変更しようとする動作は定義されていません。)
Eric Postpischil

@エリック、私は質問が標準またはタグ付きである場合にのみ特定ですlanguage-lawyer。正確さは私が大切にしている価値ですが、より複雑になることにも気づいています。ここでは、ワットOPが欲しかったと思うので、簡潔で理解しやすい文章にすることにしました。そうでないとお考えの場合は、答えてください。最初に賛成票を投じます。とにかく、コメントありがとうございます。
YSC

3

これはCで明確に定義されています。厳密なエイリアシングルールは、そのcharタイプにも、同じタイプの2つのポインターにも適用されません。

「最適化」とはどういう意味かわかりませんconst。私のコンパイラ(GCC 8.3.0 x86-64)は、どちらの場合もまったく同じコードを生成します。restrictポインタに指定子を追加すると、生成されたコードは少し良くなりますが、それはあなたのケースでは機能せず、ポインタは同じです。

(C11§6.57)

オブジェクトは、次のいずれかのタイプの左辺値式によってのみアクセスされる格納された値を持つものとします。—
オブジェクト
の有効なタイプと互換性のあるタイプ
— — オブジェクトの有効なタイプと互換性のあるタイプの修飾バージョン、—オブジェクトの有効タイプに対応する符号付きまたは符号なしタイプで
あるタイプ— オブジェクトの有効タイプの修飾バージョンに対応する符号付きまたは符号なしタイプであるタイプ
— 1つを含む集合体または共用体タイプメンバー間の前述のタイプ(再帰的には、サブアグリゲートまたは含まれるユニオンのメンバーを含む)、または
-文字タイプ。

この場合(なしrestrict)、常に121結果として得られます。

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