C でポインターを理解しようとしていますが、現在、次のことと混同しています。
char *p = "hello"
これは、 hから始まる文字配列を指す char ポインターです。
char p[] = "hello"
これはhelloを格納する配列です。
これらの両方の変数をこの関数に渡すと、どのような違いがありますか?
void printSomething(char *p)
{
printf("p: %s",p);
}
char*
とchar[]
は異なるタイプですが、すべての場合にすぐにわかるわけではありません。これは、配列がポインターに分解されるためです。つまり、型char[]
の 1 つchar*
が期待される場所に型の式が指定されている場合、コンパイラーは自動的に配列を最初の要素へのポインターに変換します。
サンプル関数printSomething
はポインターを想定しているため、配列を次のように渡そうとすると、次のようになります。
char s[10] = "hello";
printSomething(s);
コンパイラは、あなたがこれを書いたふりをします:
char s[10] = "hello";
printSomething(&s[0]);
どれどれ:
#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[] は異なる型であり、コンパイラによって異なる方法で処理されます (ポインター = アドレス + ポインターの型の表現、配列 = ポインター + 既知の場合は配列のオプションの長さ、たとえば、配列が静的に割り当てられている場合) )、詳細は規格に記載されています。そして、ランタイムのレベルでは、それらの間に違いはありません(アセンブラーでは、まあ、ほとんど、以下を参照してください)。
Q : これらの初期化の違いは何ですか?
char a[] = "string literal"; char *p = "string literal";
p[i] に新しい値を割り当てようとすると、プログラムがクラッシュします。
A : 文字列リテラル (C ソースの二重引用符で囲まれた文字列の正式な用語) は、2 つのわずかに異なる方法で使用できます。
- char a[] の宣言のように、char の配列の初期化子として、その配列内の文字の初期値 (および、必要に応じてそのサイズ) を指定します。
- それ以外の場所では、名前のない文字の静的配列に変わり、この名前のない配列は読み取り専用メモリに格納される可能性があるため、必ずしも変更することはできません。式のコンテキストでは、配列は通常どおり (セクション 6 を参照) すぐにポインターに変換されるため、2 番目の宣言は p を初期化し、名前のない配列の最初の要素を指すようにします。
一部のコンパイラには、文字列リテラルが書き込み可能かどうかを制御するスイッチがあり (古いコードをコンパイルするため)、一部のコンパイラには、文字列リテラルを形式的に const char の配列として処理するオプションがある場合があります (より適切なエラー キャッチのため)。
質問 1.31、6.1、6.2、6.8、および 11.8b も参照してください。
参考文献:K&R2 Sec. 5.5ページ 104
ISO秒 6.1.4、秒。6.5.7
根拠節。3.1.4
H&S Sec. 2.7.4 pp.31-2
Cのchar配列とcharポインタの違いは何ですか?
C99 N1256 ドラフト
文字列リテラルには、次の 2 つの異なる用途があります。
初期化char[]
:
char c[] = "abc";
これは「より魔法」であり、6.7.8/14「初期化」で説明されています。
文字型の配列は、文字列リテラルで初期化することができ、オプションで中括弧で囲みます。文字列リテラルの連続する文字 (空きがある場合、または配列のサイズが不明な場合は、終端の null 文字を含む) は、配列の要素を初期化します。
したがって、これは次のショートカットです。
char c[] = {'a', 'b', 'c', '\0'};
他の通常の配列と同様に、c
変更できます。
それ以外の場合: 以下を生成します。
だからあなたが書くとき:
char *c = "abc";
これは次のようになります。
/* __unnamed is magic because modifying it gives UB. */
static char __unnamed[] = "abc";
char *c = __unnamed;
char[]
from からto への暗黙的char *
なキャストに注意してください。これは常に有効です。
次に、 を変更すると、 UB であるc[0]
も変更されます。__unnamed
これは、6.4.5「文字列リテラル」で文書化されています。
5 変換フェーズ 7 では、値ゼロのバイトまたはコードが、文字列リテラルまたはリテラルから生じる各マルチバイト文字シーケンスに追加されます。次に、マルチバイト文字シーケンスを使用して、シーケンスを格納するのに十分な長さの静的ストレージ期間の配列を初期化します。文字列リテラルの場合、配列要素は 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";
「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
andを置くことに注意してください。.text
これは、次の方法で確認できます。
readelf -l a.out
を含む:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
p
最初のポイントが指す文字列定数の内容を変更することはできません。2 つ目p
は、文字列定数で初期化された配列で、その内容を変更できます。
このような場合でも、結果は同じです。つまり、文字列の最初の文字のアドレスを渡すことになります。
ただし、宣言は明らかに同じではありません。
次の例では、文字列と文字ポインター用にメモリを確保し、文字列の最初の文字を指すようにポインターを初期化します。
char *p = "hello";
以下は、文字列のためだけにメモリを確保します。したがって、実際には使用するメモリが少なくなります。
char p[10] = "hello";
私が覚えている限り、配列は実際にはポインターのグループです。例えば
p[1]== *(&p+1)
は真実の声明です
char p[3] = "hello"
? char p[6] = "hello"
C では「文字列」の末尾に「\0」文字があることに注意してください。
とにかく、C の配列は、メモリ内の調整オブジェクトの最初のオブジェクトへの単なるポインターです。唯一の異なる はセマンティクスにあります。ポインターの値を変更してメモリ内の別の場所を指すことができますが、配列は作成後、常に同じ場所を指します。
また、配列を使用する場合、「新規」および「削除」は自動的に行われます。