web-dev-qa-db-ja.com

char *が読み取り専用の場合、なぜそれらを上書きできるのですか?

私のコースでは、char *は静的/読み取り専用であることがわかったので、定義した後は編集できないと思いました。しかし、私が実行すると:

char* fruit = "banana";
printf("fruit is %s\n", fruit);
fruit = "Apple";
printf("fruit is %s\n", fruit);

それからそれはうまくコンパイルされて私に与えます:

fruit is banana
fruit is Apple

どうして?読み取り専用であることの意味を誤解しましたか?これが明らかな場合は申し訳ありませんが、私はコーディングに不慣れで、オンラインで答えを見つけることができません。

16
sally2000

提示されたコードスニペットは、文字列リテラル自体を変更しません。ポインタfruitに格納されている値のみを変更します。

あなたはこれらの線を想像することができます

char* fruit = "banana";
fruit = "Apple";

次の方法

char unnamed_static_array_banana[] = { 'b', 'a', 'n', 'a', 'n', 'a', '\0' };
char *fruit = &unnamed_static_array_banana[0];
char unnamed_static_array_Apple[]  = { 'a', 'p', 'p', 'l', 'e', '\0' };
fruit = &unnamed_static_array_Apple[0];

これらのステートメントは、文字列リテラルに対応する配列を変更しません。

一方、あなたが書き込もうとした場合

char* fruit = "banana";
printf("fruit is %s\n", fruit);
fruit[0] = 'h';
^^^^^^^^^^^^^^
printf("fruit is %s\n", fruit);

つまり、(文字列リテラルの最初の文字を指す)ポインタを使用して文字列リテラルを変更しようとした場合、プログラムの動作は未定義でした。

C標準から(6.4.5文字列リテラル)

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

21

プログラムでは、式"banana"は、プログラムイメージ内の文字列リテラルオブジェクトである文字配列を示します。式の値は、タイプchar *、または「文字へのポインター」です。ポインタは、その配列の最初のバイトである文字'b'を指します。

char *fruit変数も「文字へのポインタ」タイプであり、この式から初期値を取得します。データ自体ではなく、データへのポインタのコピーに初期化されます。単にbを指しているだけです。

"Apple"fruitに割り当てると、ポインタ値が別の値に置き換えられるだけなので、別のリテラル配列を指すようになります。

データ自体を変更するには、次のような式が必要です。

char *fruit = "banana";
fruit[0] = 'z';  /* try to turn "banana" into "zanana" */

ISO C規格によれば、この動作は定義されていません。 could"banana"配列は読み取り専用ですが、必須ではありません。

C実装では、文字列リテラルを書き込み可能にすることも、オプションにすることもできます。

(文字列リテラルを変更できる場合、それはすべてがうまくいくという意味ではありません。まず、プログラムはISO Cに従ってまだ十分に定義されていません:移植性がありません。次に、Cコンパイラはリテラルをマージできますこれは、同じストレージに共通のコンテンツがあることを意味します。これは、プログラム内で2回出現する"banana"が実際にはまったく同じ配列である可能性があることを意味します。さらに、プログラム内のどこかに出現する文字列リテラル"nana"は配列のサフィックス"banana"は他の場所で発生します。つまり、同じストレージを共有します。リテラルを変更すると、驚くべき効果が得られます。変更は他のリテラルに表示される可能性があります。)

また、「静的」と「読み取り専用」は同義ではありません。 Cのほとんどの静的ストレージは、実際には変更可能です。次のような文字列を保持する変更可能な静的文字配列を作成できます。

/* at file scope, i.e. outside of any function */
char fruit[] = "banana";

または:

{
  /* in a function */
  static fruit[] = "banana";

配列サイズを省略すると、初期化文字列リテラルから自動的にサイズが変更され、null終了バイト用のスペースが含まれます。関数では、配列を静的ストレージに配置するためにstaticが必要です。そうでない場合は、ローカル変数を取得します。

これらの配列は変更できます。 fruit[0] = 'z'は明確に定義された動作です。

また、これらの状況では、"banana"は文字配列を示しません。配列は変数fruitです。 "banana"式は、配列の初期値を示す構文の一部にすぎません。

char *fruit = "banana";  // "banana" is an object in program image
                         // initial value is a pointer to that object

char fruit_array[] = "Apple"; // "Apple" is syntax giving initial value
9
Kaz

fruitオブジェクトは書き込み可能です-別の文字列リテラルを指すように設定できます。

文字列リテラル"banana"および"Apple"書き込み可能ではありません。文字列リテラルを指すようにfruitを変更できますが、そうする場合は、fruitポイントする:を変更しようとしないでください。

char *fruit = "banana"; // fruit points to first character of string literal
fruit = "Apple";        // okay, fruit points to first character of different string literal
*fruit = 'A';           // not okay, attempting to modify contents of string literal
fruit[1] = 'P';         // not okay, attempting to modify contents of string literal

文字列リテラルの内容を変更しようとすると、未定義の動作が発生します。コードが期待どおりに機能したり、ランタイムエラーが発生したり、まったく予期しないことが発生したりする可能性があります。安全のために、文字列リテラルを指すように変数を定義している場合は、それをconstと宣言する必要があります。

const char *fruit = "banana";  // can also be written char const *

fruitを割り当てて、別の文字列を指すようにすることもできます。

fruit = "Apple";

しかし、fruitが指すものを変更しようとすると、コンパイラーはあなたに怒鳴ります。

1つの特定の文字列リテラルのみを指すことができるポインタを定義する場合は、const-ポインタを修飾することもできます。

const char * const fruit = "banana"; // can also be written char const * const

このように、fruitが指すものに書き込もうとすると、またはfruitを別のオブジェクトを指すように設定しようとすると、コンパイラはあなたに怒鳴ります。

4
John Bode

基本的に、あなたが実行するとき

char* fruit = "banana";

「バナナ」の最初の文字へのポインタfruitを設定します。印刷する場合、Cは基本的に「b」から始まり、最後に\0null文字がヒットするまで文字を印刷し続けます。

それから言うことによって

fruit = "Apple";

ポインタfruitを変更して、「Apple」の最初の文字を指すようにしました。

3
Jeeter

まず第一に、char*は読み取り専用ではありません。 char * constsはです。そして、それらはchar const *とは異なります。また、リテラル文字列( "banana"など)は必須ですが、必ずしもそうとは限りません。

char * const  cpfruit = "banana";
cpfruit = "Apple";        // error

char const * cpfruit = "banana";
cpfruit[0] = 'x';        // error

char * ncfruit = "banana";
ncfruit[0] = 'x';        // compile will allow, but may cause run-time error.
3
James Curran

変数fruitを別の文字列にポイントしています。アドレス(場所)を上書きするだけです。コンパイラは、定数文字列「banana」と「Apple」を確認し、それらを別々にプログラムメモリに保存します。文字列「banana」がアドレス1にあるメモリセルに送られ、「Apple」がメモリアドレス2に保存されたとします。今あなたがするとき:

fruit = "banana";

コンパイラは1を変数fruitに割り当てるだけです。つまり、文字列bananaを含むアドレス1を指します。あなたがするとき:

fruit = "Apple";

コンパイラは2変数fruitを割り当てます。これは、文字列Appleが格納されているaddess2を指すことを意味します。

2
arminb

あなたのコースがあなたに教えたことは正しいです!

そもそもchar* fruit = "banana"を定義すると、基本的にfruit定数文字へのポインタになります。文字列の7バイト(ヌル終了を含む)は、オブジェクトファイルの.roセクションにあります(セクション名はプラットフォームによって明らかに異なります)。

Charポインタフルーツを「Apple」にリセットすると、「Apple」を含む読み取り専用セクションの別のメモリ位置を指しているだけです。

基本的に、果物が定数であると言うとき、それはfruitconstメモリへのポインタであることを指します。あなたがそれをconst pointer to a const stringとして定義したとしたら:-
char* const fruit = "banana";
コンパイラは、「Apple」にリセットするのを阻止していたでしょう。

2
Zakir

c文字列、別名char配列を、二重引用符"..."で、次の形式で定義する場合:

char * <varName> = "<someString>"

配列の要素のみが不変です(内容は変更できません)。言い換えると、<varName>にはconst char *タイプ(読み取り専用メモリへの可変ポインタ)があります。また、二重引用符<varName> = "<otherString>"を使用して代入演算子を呼び出すたびに、ポインター値が自動的に変更されます。以下の例は、さまざまな可能性の完全な概要を示しています。

#include <stdio.h>

int main()
{
    char * var_1 = "Lorem";
    printf("1. %s , %p\n", var_1, var_1); // --> 1. Lorem , 0x400640

    var_1 = "ipsu";
    printf("2. %s , %p\n", var_1, var_1); // --> 2. ipsu , 0x400652

    // var_1[0] = 'x'; // --> Segmentation fault

    var_1++;
    printf("3. %s , %p\n", var_1, var_1); // --> 3. psu , 0x400653

    char var_2[] = {'L', 'o', 'r', 'e', 'm', '\0'};
    printf("4. %s , %p\n", var_2, var_2); // --> 4. Lorem , 0x7ffed0fc5381

    var_2[0] = 'x';
    printf("5. %s , %p\n", var_2, var_2); // --> 5. xorem , 0x7ffed0fc5381

    // var_2++; //error: lvalue required as increment operand

    char var_3[] = "Lorem";
    printf("6. %s , %p\n", var_3, var_3); // --> 6. Lorem , 0x7ffe36a42d5c

    // var_3 = "ipsu"; // --> error: assignment to expression with array type

    var_3[0] = 'x';
    printf("7. %s , %p\n", var_3, var_3); // --> 7. xorem , 0x7ffe36a42d5c

    char * const var_4 = "Lorem";

    // var_4 = "ipsu"; // --> error: assignment of read-only variable

    // var_4[0] = 'x'; // --> Segmentation fault

    char const * var_5 = "Lorem";
    printf("8. %s , %p\n", var_5, var_5); // --> Lorem , 0x400720

    var_5 = "ipsu";
    printf("9. %s , %p\n", var_5, var_5); // --> ipsu , 0x400732

    // var_5[0] = 'x'; // --> error: assignment of read-only location

    const char * var_6 = "Lorem";
    printf("10. %s , %p\n", var_6, var_6); // --> 10. Lorem , 0x400760

    var_6 = "ipsu";
    printf("11. %s , %p\n", var_6, var_6); // --> 11. ipsu , 0x400772

    // var_6[0] = 'x'; // --> error: assignment of read-only location

    const char const * var_7 = "Lorem"; // clang only --> warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier]
    printf("12. %s , %p\n", var_7, var_7); // --> 12. Lorem , 0x400760

    var_7 = "ipsu";
    printf("13. %s , %p\n", var_7, var_7); // --> 13. ipsu , 0x400772

    // var_7[0] = 'x'; // --> error: assignment of read-only location

    char const const * var_8 = "Lorem"; // clang only --> warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier]
    printf("14. %s , %p\n", var_8, var_8); // --> 14. Lorem , 0x400790

    var_8 = "ipsu";
    printf("15. %s , %p\n", var_8, var_8); // --> 15. ipsu , 0x4007a2

    // var_8[0] = 'x'; // --> error: assignment of read-only location

    char const * const var_9 = "Lorem";

    // var_9 = "ipsu"; // --> error: assignment of read-only variable

    // var_9[0] = 'x'; // --> error: assignment of read-only location

    const char var_10[] = {'L', 'o', 'r', 'e', 'm', '\0'};

    // var_10[0] = 'x'; // --> error: assignment of read-only location

    // var_10++; // --> error: lvalue required as increment operand

    char const var_11[] = {'L', 'o', 'r', 'e', 'm', '\0'};

    // var_11[0] = 'x'; // --> error: assignment of read-only location 

    // var_11++; // --> error: lvalue required as increment operand

    const char var_12[] = "Lorem";

    // var_12[0] = 'x'; // --> error: assignment of read-only location

    // var_12++; // --> error: lvalue required as increment operand

    char const var_13[] = "Lorem";

    // var_13[0] = 'x'; // --> error: assignment of read-only location

    // var_13++; // --> error: lvalue required as increment operand


    return 0;
}

このコードは、GCC、Clang、およびVisualStudioでテストされました。

基本的に、3つの可能性があります。

  • 不変のポインタ、可変のコンテンツ

    • char <varName>[] = {'L', 'o', 'r', 'e', 'm', '\0'};
    • char <varName>[] = "Lorem";
  • 可変ポインタ、不変コンテンツ

    • char * <varName> = "Lorem";
    • char const * <varName> = "Lorem";
    • const char * <varName> = "Lorem";
    • const char const * <varName> = "Lorem";
    • char const const * <varName> = "Lorem";
  • 不変のポインタ、不変のコンテンツ

    • char * const <varName> = "Lorem";
    • char const * const <varName> = "Lorem";
    • const char * const <varName> = "Lorem";
    • const char <varName>[] = {'L', 'o', 'r', 'e', 'm', '\0'};
    • char const <varName>[] = {'L', 'o', 'r', 'e', 'm', '\0'};
    • const char <varName>[] = "Lorem";
    • char const <varName>[] = "Lorem";

結論:

  • <typing> <varName>[] = <string>は常に不変のポインタを返し、コンテンツの可変性は<array>形式("Lorem"または{'L', 'o', 'r', 'e', 'm', '\0'})から独立しています。
  • <typing> * <varName> = "someString"は常に不変のコンテンツを返します
  • <typing> * const <varName> = "someString"は常に不変のコンテンツとポインタの両方を返します
  • char const <other>char const <other>const char const <other>、およびchar const const <other>は、常に不変のコンテンツを作成します。

私はCの配列の振る舞いを要約しようとしました ここ 詳細に。

2
Foad

char *p="banana";を使用すると、文字列バナナは読み取り専用のメモリ位置に格納されます。その後、p="Apple";と入力すると、文字列Appleが他のメモリ位置に格納され、ポインタが新しいメモリ位置を指しているようになります。

これは、各割り当ての直後にpを出力することで確認できます。

#include<stdio.h>
int main(void)
{
    char *p = "Banana";
    printf("p contains address of string constant 'Banana' at 0x%p\n", p);

    p="Apple";
    printf("p contains address of string constant 'Apple' at 0x%p\n", p);

}
1
Sachin Mathad