web-dev-qa-db-ja.com

C:charポインターと配列の違い

考慮してください:

char amessage[] = "now is the time";
char *pmessage = "now is the time";

The C Programming Language 、2nd Editionから、上記の2つのステートメントは同じことをしないと読みました。

配列はポインターを操作してデータを保存する便利な方法だといつも思っていましたが、そうではありません。Cの配列とポインターの「重要な」違いは何ですか?

135
Midnight Blue

本当ですが、微妙な違いです。本質的に、前者:

char amessage[] = "now is the time";

メンバーが現在のスコープのスタックスペースに存在する配列を定義します。

char *pmessage = "now is the time";

現在のスコープのスタックスペースに存在するが、他の場所のメモリを参照するポインタを定義します(この場合、「現在」はメモリ内のどこかに格納され、通常は文字列テーブル)。

また、2番目の定義(明示的なポインター)に属するデータは現在のスコープのスタックスペースに格納されないため、格納される場所は正確に指定されておらず、変更しないでください。

編集:Mark、GMan、およびPavelが指摘したように、これらの変数のいずれかでアドレス演算子を使用する場合にも違いがあります。たとえば、&pmessageはchar **型のポインター、またはcharsへのポインターへのポインターを返しますが、&amessageはchar(*)[16]型のポインター、または16文字の配列へのポインターを返します(これは、 char **、litbが指摘しているように2回参照解除する必要があります)。

95
Walt W

2つの宣言の結果を示す仮想メモリマップを次に示します。

                0x00  0x01  0x02  0x03  0x04  0x05  0x06  0x07
    0x00008000:  'n'   'o'   'w'   ' '   'i'   's'   ' '   't'
    0x00008008:  'h'   'e'   ' '   't'   'i'   'm'   'e'  '\0'
        ...
amessage:
    0x00500000:  'n'   'o'   'w'   ' '   'i'   's'   ' '   't'
    0x00500008:  'h'   'e'   ' '   't'   'i'   'm'   'e'  '\0'
pmessage:
    0x00500010:  0x00  0x00  0x80  0x00

文字列リテラル "now is the time"は、メモリアドレス0x00008000にcharの16要素配列として格納されます。このメモリは書き込み可能でない場合があります。そうではないと想定するのが最善です。文字列リテラルの内容を変更しないでください。

宣言

char amessage[] = "now is the time";

メモリアドレス0x00500000にcharの16要素配列を割り当て、文字列リテラルのcontentsをコピーします。このメモリは書き込み可能です。メッセージの内容を心ゆくまで変更できます。

strcpy(amessage, "the time is now");

宣言

char *pmessage = "now is the time";

メモリアドレス0x00500010のcharに単一のポインタを割り当て、文字列リテラルのaddressをコピーします。

Pmessageは文字列リテラルを指すため、文字列の内容を変更する必要がある関数の引数として使用しないでください。

strcpy(amessage, pmessage); /* OKAY */
strcpy(pmessage, amessage); /* NOT OKAY */
strtok(amessage, " ");      /* OKAY */
strtok(pmessage, " ");      /* NOT OKAY */
scanf("%15s", amessage);      /* OKAY */
scanf("%15s", pmessage);      /* NOT OKAY */

等々。メッセージを指すようにpmessageを変更した場合:

pmessage = amessage;

メッセージを使用できるすべての場所で使用できます。

143
John Bode

配列には要素が含まれます。ポインターがそれらを指します。

最初は言うの短い形式です

char amessage[16];
amessage[0] = 'n';
amessage[1] = 'o';
...
amessage[15] = '\0';

つまり、すべての文字を含む配列です。特別な初期化により自動的に初期化され、サイズが自動的に決定されます。配列要素は変更可能です-配列の文字を上書きできます。

2番目の形式は、文字を指すだけのポインターです。直接ではなく文字を保存します。配列は文字列リテラルであるため、ポインターを取得してそれが指す場所に書き込むことはできません

char *pmessage = "now is the time";
*pmessage = 'p'; /* undefined behavior! */

このコードはおそらくあなたのボックスでクラッシュするでしょう。しかし、その動作は定義されていないため、好きなことを行うことができます。

他の回答に便利に追加することはできませんが、 Deep C Secrets で、Peter van der Lindenがこの例を詳しく説明しています。この種の質問をしているのなら、この本が好きになると思います。


追伸pmessageに新しい値を割り当てることができます。 amessageに新しい値を割り当てることはできません。 immutableです。

6
Norman Ramsey

宣言時にサイズが利用できるように配列が定義されている場合、sizeof(p)/sizeof(type-of-array)は配列内の要素の数を返します。

5
Tamás Szelei

最初の形式(amessage)は、文字列"now is the time"のコピーを含む変数(配列)を定義します。

2番目の形式(pmessage)は、文字列"now is the time"のコピーとは異なる場所にある変数(ポインター)を定義します。

このプログラムを試してください:

#include <inttypes.h>
#include <stdio.h>

int main (int argc, char *argv [])
{
     char  amessage [] = "now is the time";
     char *pmessage    = "now is the time";

     printf("&amessage   : %#016"PRIxPTR"\n", (uintptr_t)&amessage);
     printf("&amessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&amessage[0]);
     printf("&pmessage   : %#016"PRIxPTR"\n", (uintptr_t)&pmessage);
     printf("&pmessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&pmessage[0]);

     printf("&\"now is the time\": %#016"PRIxPTR"\n",
            (uintptr_t)&"now is the time");

     return 0;
}

&amessage&amessage[0]に等しいが、&pmessageおよび&pmessage[0]には当てはまらないことがわかります。実際、amessageに格納されている文字列はスタック上に存在し、pmessageが指す文字列は他の場所に存在することがわかります。

最後のprintfは、文字列リテラルのアドレスを示しています。コンパイラが「文字列プーリング」を行う場合、「今は時間です」という文字列のコピーが1つだけあり、そのアドレスはamessageのアドレスと同じではないことがわかります。これは、amessageが初期化時に文字列のcopyを取得するためです。

最後に、ポイントは、amessageが独自のメモリ(この例ではスタック)に文字列を格納し、pmessageが他の場所に格納されている文字列を指すことです。

4
Dan Moulding

文字列「今は時間」のメモリが2つの異なる場所に割り当てられていることに加えて、配列名はポインタvalueとして機能することに注意してくださいpmessageであるポインターvariable。主な違いは、ポインター変数を変更して別の場所を指すことができ、配列ができないことです。

char arr[] = "now is the time";
char *pchar = "later is the time";

char arr2[] = "Another String";

pchar = arr2; //Ok, pchar now points at "Another String"

arr = arr2; //Compiler Error! The array name can be used as a pointer VALUE
            //not a pointer VARIABLE
4
Graphics Noob

ポインターは、メモリアドレスを保持する単なる変数です。別の問題である「文字列リテラル」を使用してplayinfであることに注意してください。インラインで説明された違い:基本的に:

#include <stdio.h>

int main ()
{

char amessage[] = "now is the time"; /* Attention you have created a "string literal" */

char *pmessage = "now is the time";  /* You are REUSING the string literal */


/* About arrays and pointers */

pmessage = NULL; /* All right */
amessage = NULL; /* Compilation ERROR!! */

printf ("%d\n", sizeof (amessage)); /* Size of the string literal*/
printf ("%d\n", sizeof (pmessage)); /* Size of pmessage is platform dependent - size of memory bus (1,2,4,8 bytes)*/

printf ("%p, %p\n", pmessage, &pmessage);  /* These values are different !! */
printf ("%p, %p\n", amessage, &amessage);  /* These values are THE SAME!!. There is no sense in retrieving "&amessage" */


/* About string literals */

if (pmessage == amessage)
{
   printf ("A string literal is defined only once. You are sharing space");

   /* Demostration */
   "now is the time"[0] = 'W';
   printf ("You have modified both!! %s == %s \n", amessage, pmessage);
}


/* Hope it was useful*/
return 0;
}
4
Sergio

2番目は、ELFの読み取り専用セクションに文字列を割り当てます。以下を試してください:

#include <stdio.h>

int main(char argc, char** argv) {
    char amessage[] = "now is the time";
    char *pmessage = "now is the time";

    amessage[3] = 'S';
    printf("%s\n",amessage);

    pmessage[3] = 'S';
    printf("%s\n",pmessage);
}

2番目の割り当てでセグメンテーション違反が発生します(pmessage [3] = 'S')。

3
benzaita

文字ポインタと配列の違い

C99 N1256ドラフト

文字列リテラルには2つの異なる使用法があります。

  1. char[]の初期化:

    char c[] = "abc";      
    

    これは「もっと魔法」であり、6.7.8/14「初期化」で説明されています。

    文字型の配列は、オプションで中括弧で囲まれた文字列リテラルによって初期化できます。文字列リテラルの連続する文字(空きがある場合や配列のサイズが不明な場合は終端のヌル文字を含む)は、配列の要素を初期化します。

    だから、これは単なるショートカットです:

    char c[] = {'a', 'b', 'c', '\0'};
    

    他の通常の配列と同様に、cは変更できます。

  2. その他の場所:それは以下を生成します:

    あなたが書くとき:

    char *c = "abc";
    

    これは次のようなものです。

    /* __unnamed is magic because modifying it gives UB. */
    static char __unnamed[] = "abc";
    char *c = __unnamed;
    

    char[]からchar *への暗黙的なキャストに注意してください。これは常に有効です。

    次に、c[0]を変更すると、__unnamed(UB)も変更されます。

    これは、6.4.5「文字列リテラル」に記載されています。

    5変換フェーズ7では、1つまたは複数の文字列リテラルから生じる各マルチバイト文字シーケンスに、値ゼロのバイトまたはコードが追加されます。次に、マルチバイト文字シーケンスを使用して、シーケンスを格納するのに十分な静的ストレージの継続時間と長さの配列を初期化します。文字列リテラルの場合、配列要素はchar型であり、マルチバイト文字シーケンスの個々のバイトで初期化されます[...]

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

6.7.8/32「初期化」に直接的な例を示します。

例8:宣言

char s[] = "abc", t[3] = "abc";

要素が文字列リテラルで初期化される「プレーン」文字配列オブジェクトsおよびtを定義します。

この宣言は次と同じです

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };

配列の内容は変更可能です。一方、宣言

char *p = "abc";

タイプ「charへのポインター」でpを定義し、エレメントが文字ストリング・リテラルで初期化される、長さ4のタイプ「charの配列」を持つオブジェクトを指すように初期化します。 pを使用して配列の内容を変更しようとした場合、動作は未定義です。

GCC 4.8 x86-64 ELF実装

プログラム:

#include <stdio.h>

int main(void) {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}

コンパイルと逆コンパイル:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o

出力に含まれるもの:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

結論:GCCはchar*ではなく.rodataセクションに.textを保存します。

char[]に対して同じことを行う場合:

 char s[] = "abc";

私達は手に入れました:

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)

そのため、(%rbpに関連して)スタックに保存されます。

ただし、デフォルトのリンカースクリプトは、.rodata.textを同じセグメントに配置します。これらのセグメントには、実行権限はありますが書き込み権限はありません。これは次の場合に確認できます。

readelf -l a.out

を含む:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

上記の答えはあなたの質問に答えたに違いありません。しかし、デニスリッチーirが執筆した The Development of C Language の「Embryonic C」の段落を読むことをお勧めします。

1
abcoep

この行の場合:char amessage [] = "now is the time";

コンパイラは、「今は時間です」という文字を保持している配列の先頭へのポインタとしてのメッセージの使用を評価します。コンパイラは、「現在の時間」にメモリを割り当て、「現在の時間」という文字列でメモリを初期化します。メッセージは常にそのメッセージの開始を参照するため、そのメッセージが保存されている場所を知っています。メッセージには新しい値が与えられない可能性があります。変数ではなく、文字列の名前「今は時間です」です。

次の行:char * pmessage = "now is the time";

文字列「now is the time」の開始アドレスのinitialized(初期値を指定)である変数pmessageを宣言します。メッセージとは異なり、pmessageには新しい値を指定できます。この場合、前の場合と同様に、コンパイラはメモリのどこかに「今が今」です。たとえば、これにより、pmessageは「is the time」で始まる「i」を指すようになります。 pmessage = pmessage + 4;

0
Ron Jacobs