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


216

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

  • char *p = "hello"

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

  • char p[] = "hello"

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

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

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

5
これは無効です。char p[3] = "hello";初期化文字列は、宣言した配列のサイズに対して長すぎます。打ち間違え?
コーディグレイ

16
それともchar p[]="hello";十分でしょう!
2014


1
Cのchar s []とchar * sの違い何ですか?確かに、これは関数パラメーターについても具体的に尋ねますが、それはchar特定ではありません。
Ciro Santilli郝海东冠状病六四事件法轮功

1
それらが根本的に異なることを理解する必要があります。この唯一の共通点は、配列p []のベースがconstポインターであり、ポインターを介して配列p []にアクセスできるようにすることです。p []自体は文字列のメモリを保持しますが、* pはちょうど1つのCHARの最初の要素のアドレスを指します(つまり、すでに割り当てられている文字列のベースを指します)。これをよりよく説明するために、以下を検討してください。char * cPtr = {'h'、 'e'、 'l'、 'l'、 'o'、 '\ 0'}; ==>これはエラーです。cPtrは文字charのみへのポインタであるためですcBuff [] = {'h'、 'e'、 'l'、 'l'、 'o'、 '\ 0'}; ==>これは大丈夫です、bcos cBuff自体はchar配列です
Ilavarasan '27 / 10/27

回答:


222

char*char[] は異なるタイプですが、すべてのケースですぐにわかるわけではありません。これは、配列がポインタ分解されるためです。つまり、型の式char[]が提供されている場合、char*が指定されている場合、コンパイラは配列をその最初の要素へのポインタに自動的に変換します。

関数の例でprintSomethingはポインタが必要なので、次のように配列を渡そうとすると、

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

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

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

2012年から今までに変わったことはありますか?文字配列の場合、「s」は配列全体を出力します。つまり、「hello」
Bhanu Tez

@BhanuTezいいえ、データがどのように保存され、何がデータに対して行われるかは、個別の問題です。この例ではprintf%sフォーマット文字列を処理する方法であるため、文字列全体を出力します。指定されたアドレスから開始し、nullターミネータが見つかるまで続行します。たとえば、1文字だけを印刷したい場合は、%cフォーマット文字列を使用できます。
iX3

char []配列の場合のようにchar *p = "abc";、NULL文字\0が自動的に追加されるかどうかを確認したいだけですか?
KPMG

なぜ私は設定char *name; name="123";できるがintタイプで同じことができるのですか?そして、を使用%cして印刷したname後、出力は読み取り不能な文字列になります
TomSawyer

83

どれどれ:

#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.5ページ 104

ISO Sec。6.1.4、セクション 6.5.7

理論的根拠 3.1.4

H&S Sec。2.7.4 31-2ページ


sizeof(q)で、@ Jonが彼の回答で言及しているように、なぜqがポインタに減衰しないのですか?
garyp

@garyp qは、sizeofが関数ではなく演算子であるため、ポインターに減衰しません(sizeofが関数であっても、qは、関数がcharポインターを期待している場合にのみ減衰します)。
GiriB

おかげで、しかし、printfの( "%uは\ n"は代わりのprintfの( "%ZU \ n"は、私はあなたがZを削除するべきだと思います。
ザカリア

33

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

C99 N1256ドラフト

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

  1. 初期化char[]

    char c[] = "abc";      

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

    文字型の配列は、文字列リテラルで初期化でき、オプションで中括弧で囲むことができます。文字列リテラルの連続する文字(スペースがある場合、または配列のサイズが不明の場合は、終了のnull文字を含む)は、配列の要素を初期化します。

    したがって、これは次のショートカットにすぎません。

    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つまたは複数の文字列リテラルから生成される各マルチバイト文字シーケンスに、値0のバイトまたはコードが追加されます。次に、マルチバイト文字シーケンスを使用して、シーケンスを含むのに十分な静的ストレージ期間と長さの配列を初期化します。文字列リテラルの場合、配列要素はchar型であり、マルチバイト文字シーケンスの個々のバイトで初期化されます[...]

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

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

例8:宣言

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

「プレーン」なchar配列オブジェクトsを定義し、tその要素は文字列リテラルで初期化されます。

この宣言は

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

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

char *p = "abc";

pタイプ "pointer to char"で定義し、要素が文字列リテラルで初期化される長さ4のタイプ "array of 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


9

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


6

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

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

次のコードは、文字列と文字ポインタのメモリを確保し、文字列の最初の文字を指すようにポインタを初期化します。

char *p = "hello";

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

char p[10] = "hello";

codeplusplus.blogspot.com/2007/09/…「ただし、変数を初期化すると、配列のパフォーマンスとスペースが大幅に低下します」
leef

@leef:変数がどこにあるかによります。静的メモリ内にある場合、配列とデータをEXEイメージに格納でき、初期化をまったく必要としない可能性があると思います。データが割り当てられる必要があり、その後、静的なデータをにコピーする必要がある場合はそれ以外の場合は、はい、それは確かに遅くなることができます。
ジョナサン・ウッド

3

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

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

真の声明です


2
配列をメモリブロックのアドレスへのポインタであると説明します。したがって、なぜ*(arr + 1)の2番目のメンバーに移動しarrます。たとえば*(arr)、32ビットのメモリアドレスを指している場合はbfbcdf5e、(2番目のバイト)を*(arr + 1)指しbfbcdf60ます。したがって、OSがsegfaultを実行しない場合、アレイのスコープから外れると奇妙な結果がもたらされるのはなぜですか。int a = 24;がアドレスbfbcdf62にある場合、segfaultが最初に発生しないと仮定すると、アクセスarr[2]が戻る可能性があり24ます。
ブレーデンベスト14

3

APUE、セクション5.14:

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

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

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


1

char p[3] = "hello"char p[6] = "hello"Cの「文字列」の最後に「\ 0」文字があることに注意してください。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.