web-dev-qa-db-ja.com

Cのchar配列とcharポインタの違いは何ですか?

私はC言語でポインタを理解しようとしていますが、現在以下のものと混同しています。

  • char *p = "hello"
    

    これはhから始まる文字配列を指すchar型のポインタです。

  • char p[] = "hello"
    

    これはhelloを格納する配列です。

この両方の変数をこの関数に渡したときの違いは何ですか?

void printSomething(char *p)
{
    printf("p: %s",p);
}
189
diesel

char*char[]は異なる型ですが、すべての場合ですぐには明らかになりません。これは、配列がポインタに分解されるためです。つまり、char[]型の式が期待される場所に、char*型の式が指定されている場合、コンパイラは自動的に配列を変換します。最初の要素へのポインタに。

あなたの例の関数printSomethingはポインタを期待しているので、あなたがこのようにそれに配列を渡そうとするならば:

char s[10] = "hello";
printSomething(s);

コンパイラは、あなたがこれを書いたふりをします。

char s[10] = "hello";
printSomething(&s[0]);
202
Jon

どれどれ:

#include <stdio.h>
#include <string.h>

int main()
{
    char *p = "hello";
    char q[] = "hello"; // no need to count this

    printf("%zu\n", sizeof(p)); // => size of pointer to char -- 4 on x86, 8 on x86-64
    printf("%zu\n", sizeof(q)); // => size of char array in memory -- 6 on both

    // size_t strlen(const char *s) and we don't get any warnings here:
    printf("%zu\n", strlen(p)); // => 5
    printf("%zu\n", strlen(q)); // => 5

    return 0;
}

foo *とfoo []は異なる型で、コンパイラによって処理方法が異なります(ポインタ=アドレス+ポインタの型の表現、配列=ポインタ+任意の長さの配列(既知の場合、たとえば、配列が静的に割り当てられている場合)。 )、詳細は標準で見つけることができます。そして実行時のレベルでは、それらの間に違いはありません(アセンブラでは、たいてい、下記を参照してください)。

また、 C FAQ に関連する 質問 があります。

Q:これらの初期化の違いは何ですか?

char a[] = "string literal";   
char *p  = "string literal";   

P [i]に新しい値を代入しようとすると、プログラムがクラッシュします。

A:文字列リテラル(Cソースでは二重引用符で囲まれた文字列の正式な用語)は、少し異なる2つの方法で使用できます。

  1. Char a []の宣言のように、char配列の初期化子として、その配列内の文字の初期値(および必要に応じてそのサイズ)を指定します。
  2. それ以外の場所では、名前のない静的な文字配列になります。この名前のない配列は読み取り専用メモリに格納される可能性があるため、必ずしも変更することはできません。式の文脈では、通常通り配列はポインタに変換されます(6節を参照)ので、2番目の宣言は名前のない配列の最初の要素を指すようにpを初期化します。

(古いコードをコンパイルするために)文字列リテラルが書き込み可能かどうかを制御するスイッチを持つコンパイラもあれば、文字列リテラルを形式的にconst charの配列として扱うオプションを持つものもあります。

質問1.31、6.1、6.2、6.8、および11.8bも参照してください。

参考文献:K&R2 Sec。 5.5p。 104

ISOセクション6.1.4、Sec。 6.5.7

理論的根拠3.1.4

H&Sセクション2.7.4 pp。31-2

74
JJJ

Cのchar配列とcharポインタの違いは何ですか?

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 of array」でオブジェクトが文字列リテラルで初期化されるオブジェクトを指すように初期化します。 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

あなたは文字列定数の内容を変更することは許されていません。それは最初のpが指し示すものです。 2番目のpは文字列定数で初期化された配列です。あなたはその内容を変更することができます

9
potrzebie

このような場合でも、効果は同じです。文字列の最初の文字のアドレスを渡すことになります。

宣言は明らかに同じではありません。

次の例では、文字列と文字ポインタ用のメモリを確保してから、文字列の先頭文字を指すようにポインタを初期化します。

char *p = "hello";

以下は文字列専用のメモリを確保していますが。そのため、実際に使用するメモリが少なくて済みます。

char p[10] = "hello";
6
Jonathan Wood

私が覚えている限りでは、配列は実際にはポインタのグループです。例えば

p[1]== *(&p+1)

本当の声明です

2
CosminO

char p[3] = "hello"? Cの「文字列」の末尾に「\ 0」文字があることを覚えておいてくださいchar p[6] = "hello".

とにかく、Cの配列は、メモリ内のadjustオブジェクトの最初のオブジェクトへのポインターにすぎません。唯一異なるのはセマンティクスです。メモリ内の別の場所を指すようにポインタの値を変更できますが、配列は作成後、常に同じ場所を指します。
配列を使用する場合も、「新規」および「削除」が自動的に行われます。

1
Roee Gavirel

APUE、セクション5.14から。

char    good_template[] = "/tmp/dirXXXXXX"; /* right way */
char    *bad_template = "/tmp/dirXXXXXX";   /* wrong way*/

...最初のテンプレートでは、配列変数を使用しているため、名前はスタックに割り当てられます。ただし、2番目の名前にはポインタを使用します。この場合、ポインタ自体のメモリだけがスタックに存在します。コンパイラーは、ストリングが実行可能ファイルの読み取り専用セグメントに保管されるようにします。 mkstemp関数が文字列を変更しようとすると、セグメンテーション違反が発生します。

引用されたテキストは@Ciro Santilliの説明と一致します。

1
Rick