私のコースでは、char *は静的/読み取り専用であることがわかったので、定義した後は編集できないと思いました。しかし、私が実行すると:
char* fruit = "banana";
printf("fruit is %s\n", fruit);
fruit = "Apple";
printf("fruit is %s\n", fruit);
それからそれはうまくコンパイルされて私に与えます:
fruit is banana
fruit is Apple
どうして?読み取り専用であることの意味を誤解しましたか?これが明らかな場合は申し訳ありませんが、私はコーディングに不慣れで、オンラインで答えを見つけることができません。
提示されたコードスニペットは、文字列リテラル自体を変更しません。ポインタ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要素に適切な値がある場合、これらの配列が異なるかどうかは指定されていません。 プログラムがそのような配列を変更しようとした場合、動作は定義されていません。
プログラムでは、式"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
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
を別のオブジェクトを指すように設定しようとすると、コンパイラはあなたに怒鳴ります。
基本的に、あなたが実行するとき
char* fruit = "banana";
「バナナ」の最初の文字へのポインタfruit
を設定します。印刷する場合、Cは基本的に「b」から始まり、最後に\0
null文字がヒットするまで文字を印刷し続けます。
それから言うことによって
fruit = "Apple";
ポインタfruit
を変更して、「Apple」の最初の文字を指すようにしました。
まず第一に、char*
は読み取り専用ではありません。 char * const
sはです。そして、それらは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.
変数fruit
を別の文字列にポイントしています。アドレス(場所)を上書きするだけです。コンパイラは、定数文字列「banana」と「Apple」を確認し、それらを別々にプログラムメモリに保存します。文字列「banana」がアドレス1
にあるメモリセルに送られ、「Apple」がメモリアドレス2
に保存されたとします。今あなたがするとき:
fruit = "banana";
コンパイラは1
を変数fruit
に割り当てるだけです。つまり、文字列banana
を含むアドレス1
を指します。あなたがするとき:
fruit = "Apple";
コンパイラは2
変数fruit
を割り当てます。これは、文字列Apple
が格納されているaddess2
を指すことを意味します。
あなたのコースがあなたに教えたことは正しいです!
そもそもchar* fruit = "banana"
を定義すると、基本的にfruit
が定数文字へのポインタになります。文字列の7バイト(ヌル終了を含む)は、オブジェクトファイルの.ro
セクションにあります(セクション名はプラットフォームによって明らかに異なります)。
Charポインタフルーツを「Apple」にリセットすると、「Apple」を含む読み取り専用セクションの別のメモリ位置を指しているだけです。
基本的に、果物が定数であると言うとき、それはfruit
がconst
メモリへのポインタであることを指します。あなたがそれをconst pointer to a const string
として定義したとしたら:-char* const fruit = "banana";
コンパイラは、「Apple」にリセットするのを阻止していたでしょう。
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の配列の振る舞いを要約しようとしました ここ 詳細に。
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);
}