私はCのポインターを理解しようとしていますが、現在は以下と混同しています。
char *p = "hello"
これは、hから始まる文字配列を指すcharポインターです。
char p[] = "hello"
これはhelloを格納する配列です。
これらの両方の変数をこの関数に渡したときの違いは何ですか?
void printSomething(char *p)
{
printf("p: %s",p);
}
char p[]="hello";
十分でしょう!
私はCのポインターを理解しようとしていますが、現在は以下と混同しています。
char *p = "hello"
これは、hから始まる文字配列を指すcharポインターです。
char p[] = "hello"
これはhelloを格納する配列です。
これらの両方の変数をこの関数に渡したときの違いは何ですか?
void printSomething(char *p)
{
printf("p: %s",p);
}
char p[]="hello";
十分でしょう!
回答:
char*
とchar[]
は異なるタイプですが、すべてのケースですぐにわかるわけではありません。これは、配列がポインタに分解されるためです。つまり、型の式char[]
が提供されている場合、char*
が指定されている場合、コンパイラは配列をその最初の要素へのポインタに自動的に変換します。
関数の例でprintSomething
はポインタが必要なので、次のように配列を渡そうとすると、
char s[10] = "hello";
printSomething(s);
コンパイラはあなたがこれを書いたふりをします:
char s[10] = "hello";
printSomething(&s[0]);
printf
、%s
フォーマット文字列を処理する方法であるため、文字列全体を出力します。指定されたアドレスから開始し、nullターミネータが見つかるまで続行します。たとえば、1文字だけを印刷したい場合は、%c
フォーマット文字列を使用できます。
char *p = "abc";
、NULL文字\0
が自動的に追加されるかどうかを確認したいだけですか?
char *name; name="123";
できるがint
タイプで同じことができるのですか?そして、を使用%c
して印刷したname
後、出力は読み取り不能な文字列になります�
。
どれどれ:
#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 Sec。6.1.4、セクション 6.5.7
理論的根拠 3.1.4
H&S Sec。2.7.4 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[]
への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
このような場合でも、効果は同じです。つまり、文字列の最初の文字のアドレスを渡すことになります。
宣言は明らかに同じではありません。
次のコードは、文字列と文字ポインタのメモリを確保し、文字列の最初の文字を指すようにポインタを初期化します。
char *p = "hello";
次は、文字列のためだけにメモリを確保します。そのため、実際には使用するメモリが少なくて済みます。
char p[10] = "hello";
私が覚えている限り、配列は実際にはポインターのグループです。例えば
p[1]== *(&p+1)
真の声明です
*(arr + 1)
の2番目のメンバーに移動しarr
ます。たとえば*(arr)
、32ビットのメモリアドレスを指している場合はbfbcdf5e
、(2番目のバイト)を*(arr + 1)
指しbfbcdf60
ます。したがって、OSがsegfaultを実行しない場合、アレイのスコープから外れると奇妙な結果がもたらされるのはなぜですか。int a = 24;
がアドレスbfbcdf62
にある場合、segfaultが最初に発生しないと仮定すると、アクセスarr[2]
が戻る可能性があり24
ます。
APUE、セクション5.14:
char good_template[] = "/tmp/dirXXXXXX"; /* right way */
char *bad_template = "/tmp/dirXXXXXX"; /* wrong way*/
...最初のテンプレートでは、配列変数を使用するため、名前はスタックに割り当てられます。ただし、2番目の名前にはポインターを使用します。この場合、ポインタ自体のメモリのみがスタックに存在します。コンパイラーは、ストリングが実行可能ファイルの読み取り専用セグメントに格納されるように調整します。とき
mkstemp
関数は文字列を変更しようとすると、セグメンテーションフォールトが発生します。
引用されたテキストは、@ Ciro Santilliの説明と一致します。
char p[3] = "hello";
初期化文字列は、宣言した配列のサイズに対して長すぎます。打ち間違え?