web-dev-qa-db-ja.com

異なる文字列が同じアドレスを持つことができる方法

Cで2つの文字列を比較するには、strcmp()関数を使用する必要があることを知っています。しかし、私は2つの文字列を==演算子で比較しようとしましたが、うまくいきました。 2つの文字列のアドレスを比較するだけなので、方法はわかりません。文字列が異なる場合は機能しません。しかし、次に文字列のアドレスを出力しました。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char* str1 = "First";
    char* str2 = "Second";
    char* str3 = "First";

    printf("%p %p %p", str1, str2, str3);

    return 0;
}

そして出力は:

00403024 0040302A 00403024
Process returned 0 (0x0)   execution time : 0.109 s
Press any key to continue.

str1str3が同じアドレスを持つことはどうして可能ですか?それらは同じ文字列を含むかもしれませんが、それらは同じ変数ではありません。

19
Drakalex

それが常にこのようになるという保証はありません。一般に、実装者は各文字列リテラルを1回だけ維持するリテラルプールを維持し、文字列リテラルを複数回使用する場合は同じアドレスが使用されます。しかし、それを別の方法で実装することもできます-標準はこれに制約を課していません。

今あなたの質問:あなたは同じ文字列リテラルを指す2つのポインターの内容を見ています。同じ文字列リテラルは同じ値をもたらしました(最初の要素へのポインタに減衰しました)。しかし、最初の段落で述べた理由により、そのアドレスは同じです。

また、%p形式指定子と(void*)キャスト。

23
user2736738

ここで興味深い点があります。実際に持っているのは、すべてconstリテラル文字列を指している3つのポインターだけです。したがって、コンパイラは"First"の単一の文字列を自由に作成し、str1str3の両方をそこに指定できます。

これは完全に異なるケースになります:

char str1[] = "First";
char str2[] = "Second";
char str3[] = "First";

リテラル文字列から3つの異なるchar配列initializedを宣言しました。テストすると、コンパイラが3つの異なる文字列に異なるアドレスを割り当てていることがわかります。

覚えておくべきこと:配列がポインタにdecayできる場合でも、ポインタと配列は別の動物です(詳細については、この post C FAQから

14
Serge Ballesta

特定の文字列リテラルがソースファイルに複数回出現する場合、コンパイラは、そのリテラルのすべてのインスタンスが同じ場所を指すように選択する場合があります。

文字列リテラルを説明する C standard のセクション6.4.5は、次のように述べています。

7これらの配列が異なるかどうかは、それらの要素が適切な値を持っていることを条件として指定されていません。プログラムがこのような配列を変更しようとした場合の動作は未定義です。

「不特定の動作」はセクション3.4.4で次のように定義されています。

未指定の値の使用、またはこの国際規格が2つ以上の可能性を提供し、どのインスタンスでも選択されるものにそれ以上の要件を課さないその他の動作

あなたの場合、文字列リテラル"First"がソースに2回出現します。そのため、コンパイラは両方に同じリテラルのインスタンスを使用し、str1str3が同じインスタンスを指すようになります。

上記のように、この動作は保証されていません。 "First"の2つのインスタンスは互いに異なる可能性があり、その結果、str1str3が異なる場所を指すようになります。文字列リテラルの2つの同一のインスタンスが同じ場所に存在するかどうかは指定されていません。

9
dbush

文字列リテラルは、C99 +複合リテラルと同様に、プールできます。つまり、ソースコードの2つの異なる出現は、実際には実行中のプログラムで1つのインスタンスのみになる可能性があります。
これは、ターゲットがハードウェアの書き込み保護をサポートしていない場合にも当てはまります。

3
Deduplicator

これが非常に困惑している理由は、「しかし、str1[1] = 'u';を設定するとどうなりますか?」 str1 == str3であるかどうか(およびリテラル"world!""hello, world!"に7を加えたアドレスであるかどうか)は実装定義なので、str3をドイツの王子に変えますか?

答えは:多分。または、str1のみが変更されるか、暗黙のうちに変更に失敗するか、読み取り専用メモリに書き込んだためにプログラムがクラッシュするか、これらのバイトを再利用したために他の微妙なバグが発生する可能性がありますさらに別の目的、または完全に別の何かのために。

char*を使用する必要がなく、文字列リテラルをconst char*に割り当てることさえできるという事実は、基本的に何十年も前のレガシーコードのせいです。 Cの最初のバージョンにはconstがありませんでした。既存のコンパイラの中には、プログラムで文字列定数を変更できるものもあれば、そうでないものもあります。標準化委員会がC++からCにconstキーワードを追加することを決定したとき、それらはすべてのコードを壊す気がなかったので、プログラムが文字列リテラルを変更するときにコンパイラーに基本的に何でもする許可を与えました。

これの実際的な影響は次のとおりです。文字列リテラルをconstではないchar*に割り当てないでください。また、文字列定数が重複する、または重複しないと決して想定しないでください(restrictでこれを保証しない限り)。このタイプのコードは1989年以来廃止されており、足元で自分を撃つことができます。文字列リテラルへのポインターが必要な場合(他の定数とメモリを共有する場合としない場合があります)、それをconst char*に保存します。なお、const char* constに保存します。変更しようとすると警告が表示されます。変更可能なcharの配列が必要な場合(他の変数にエイリアスが設定されないことが保証されている場合)、char[]に格納します。

文字列をアドレスで比較したい場合、本当に必要なのはハッシュ値または一意のハンドルです。

2
Davislor

他の答えを付け加えると、これは string interning と呼ばれる手法であり、コンパイラーはストリングが同じであることを認識し、したがって1回だけ保管します。 Javaもこれを行う傾向があります(ただし、他の投稿者が述べたように、コンパイラに依存します)。

1
wolfson