###知りたいこと
下記のコードでなぜコンパイルが通るのかが分かりません
###問題のコード
C
1#include<stdio.h> 2 3int main(void){ 4 char *result; 5 result = "Hello"; 6 printf("%s", result); 7 return 0; 8}
###疑問点
resultにはアドレスが入るのでは?
char result;としているのでresultにはアドレスが入るはずです。
そのため、文字列である"Hello"がresultに入るのはありえないと思うのですが上記のコードでなぜコンパイルが通るのでしょうか?。本来ならば文字列をresultに代入する場合result="Hello";としなければならないと思うのですが・・・。
なぜresultのprintにアスタリスクが必要?
printf("%s", result);も同様にprintf("%s", *result);とするべきのような気がします。
printf("%s", result);の場合、アドレスが表示されてしまうのではないしょうか…。
参考にしたサイト
【C言語入門】ポインタのわかりやすい使い方(配列、関数、構造体)
https://www.sejuku.net/blog/25094
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答6件
0
"Hello"
というのは、('\0'
終端の)文字列「Hello」の先頭アドレスを表します。
アドレスなので、ポインタに代入できます。
Cでは、文字列自体を=
で何かに代入することは出来ません。それ以前に、文字列自体を直接扱えません。
文字列を処理するには、1文字ずつ分解して(文字列じゃなく)文字として処理するか、文字列の先頭アドレスを関数に渡して処理してもらうか、どちらかになります。
printf
の%s
は、アドレスを受け取って、そのアドレスから'\0'
があるまで文字列が入っているとして表示します。
'A'
と"A"
の違いは理解できていますか?
投稿2019/07/22 15:37
総合スコア85890
0
cの文字列はchar(文字)の配列です
resultにはアドレスが入るのでは?
全くそのとおりで
質問のコードは"Hello"の先頭アドレスを代入してます
投稿2019/07/22 15:38
総合スコア13551
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/07/23 02:24
2019/07/23 08:05
2019/07/23 13:44
0
printf("%s", result);も同様にprintf("%s", *result);とするべきのような気がします。
resultはresult[0]と同じです。ポインタ(result)の中身()ですね。(型がcharなので1文字です)
で、じゃぁ"Hello"は何処に?・・・となると思いますがこれは処理系依存です(通常は定数として何処かに格納されます)なので、文字列リテラルは変更不可となります。
提示のコードをコンパイルした結果ですが、.asciz "Hello"として格納されているのが解ると思います。
asm
1 .text 2 .file "t.c" 3 .globl main # -- Begin function main 4 .p2align 4, 0x90 5 .type main,@function 6main: # @main 7 .cfi_startproc 8# %bb.0: 9 pushq %rax 10 .cfi_def_cfa_offset 16 11 movl $.L.str.1, %edi 12 movl $.L.str, %esi 13 xorl %eax, %eax 14 callq printf 15 xorl %eax, %eax 16 popq %rcx 17 .cfi_def_cfa_offset 8 18 retq 19.Lfunc_end0: 20 .size main, .Lfunc_end0-main 21 .cfi_endproc 22 # -- End function 23 .type .L.str,@object # @.str 24 .section .rodata.str1.1,"aMS",@progbits,1 25.L.str: 26 .asciz "Hello" 27 .size .L.str, 6 28 29 .type .L.str.1,@object # @.str.1 30.L.str.1: 31 .asciz "%s" 32 .size .L.str.1, 3 33 34 35 .ident "clang version 9.0.0 (trunk 362696)" 36 .section ".note.GNU-stack","",@progbits 37 .addrsig 38
投稿2019/07/22 16:17
総合スコア6851
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
ベストアンサー
大体おわかりになったようですけど、字面だけで理解しようとするといつまでも曖昧なまま、誤解を残したまま、になることがあります。リンク先もざっと拝見しましたが、文章とプログラムコードだけで説明しようとしています。
私のお勧めはメモリの姿をできるだけ具体的にイメージすることです。Cは(他の言語以上に)物理的なメモリを操作する言語ですから。32bit版コンピュータを想定して、図を描いてみました。
「4GBのメモリ」図が示すのは
- メモリは1バイト(8bit)ごとにアドレスが割り当てられている
- 0(0x00000000)番地から 0xFFFFFFFF 番地まで、4GBのメモリがある
変数も、プログラムも、このメモリ空間のどこかに配置されて処理が進む、それがコンピュータの動作原理です。
さて、プログラム上で "Hello" のようなものを文字列定数、或いは文字列リテラルと呼びます。文字列リテラルはメモリ上のどこかに在る、それを
- "Hello" という文字列が 0x0006A004 番地にある
- result 変数は 0x0C001248 番地にある
と仮定しました。それが「"Hello" と result変数の様子」図です。
実際のアドレスがどこかはともかく、何か具体的なアドレスを与えないと説明が面倒なのでテキトーにアドレスを決めました(少し工夫すれば実際のアドレスを確認できる)。
char *result; は変数宣言、或いは変数定義ですね。
- result という名前の変数を設ける(メモリ上に割り当てる)
- result 変数はポインタ変数である、この変数の値はアドレスである
- そのアドレスのメモリは char 型(或いは char型配列)である
という意味です。
32bitコンピュータのアドレスは 32bit ですから、result 変数には4バイトが必要です(32bit = 8bit/byte * 4byte)。この例では 0x0C001248から0x0C00124B番地までの4バイトをひとまとめに使って、ひとつのアドレスを記憶します。ポインタ変数とは、そういうものです。
なお、メモリは1バイトだけポツンとあるわけではなく、メモリは連続して存在するから、char一文字を指すポインタも、文字配列を指すポインタも、同じ格好です。
result = "Hello";
文字列である"Hello"がresultに入るのはありえないと思う
文字列リテラルの値は、その文字列が在るメモリの先頭アドレスです。図の例だと "Hello" の先頭アドレス 0x0006A004 を result 変数に代入すること。これなら、ありえなくないでしょう。
ちなみに、パソコンのCPUはリトルエンディアンなので、0x04, 0xA0, 0x06, 0x00 という順序で格納されます。そしてこれが result ポインタ変数が "Hello" 文字列をポイントした状態です。
図をもう一度ご覧ください。たったこれだけですけど、図を描いて、具体的な値を使って見なおせばクリアになると思います。
printf("%s", *result);とするべきのような気がする
ここで重要なのは** *result は result ポインタが指すメモリの値**であること。 *result の値は 72 です。72 は 'H' という値なので、
printf("%s", 72); すなわち printf("%s", 'H'); という呼出しになります。
72 という値を受け取った printf() は、運が良ければw 'H' という文字を表示できるかもしれないけれど、Hに続く0x0006A005番地の 'e' を、どうやって知るのでしょうか???
こう考えれば printf("%s", *result); では期待する動作にならないことが理解できると思います。実際は "%s" を指定する限り、72 番地から始まる文字列を表示しようとして異常終了するでしょう。
一方、printf("%s", result); は result の値 0x0006A004 を printf() に渡します。つまり printf("%s", 0x0006A004); です。文字列の先頭アドレスを渡された printf() は、そこから順次 H, e, l, l, o という文字を表示できる・・・これは既にご理解いただけたようですが。
メモリの図を描くと、こういうことも実感として理解できるし、ポインタの扱いで間違いが少なくなります。
P.S.
少し工夫すれば実際のアドレスを確認できる・・・
printf() にはポインタの値(アドレス)を表示する %p という変換指定があります。これと sizeof 演算子を使って、4行追加してみました。result の値を表示すれば "Hello" 文字列リテラルのアドレスがわかります。
C
1#include<stdio.h> 2 3int main(void){ 4 char *result; 5 result = "Hello"; 6 printf("%s", result); 7 8 printf("\n\n"); 9 printf("\"Hello\" is located at %p.\n", result); // 文字列リテラルの位置 10 printf(" result is located at %p.\n", &result); // 変数 result の位置 11 printf(" size of result is %d bytes.\n", sizeof(result)); 12 return 0; 13}
私の手元では、こんな表示になりました。
Hello "Hello" is located at 0x4006f4. result is located at 0x7ffc337f9950. size of result is 8 bytes.
result変数のサイズが8なのは64bit環境であることを示してます。もちろんデバッガでも確認できます。具体的なアドレスを知れば、より深い理解につながります。例えば、関数内のローカル変数とグローバル変数ではアドレスが大きく違うはずです。こうした情報を元にメモリの図を描きなおしてみることをお勧めします。
投稿2019/07/23 11:54
編集2019/07/23 23:43総合スコア1382
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/07/23 15:14
2019/07/24 00:34
0
resultにはアドレスが入るのでは?
その認識であってます。
コード上に"Hello"と書いた場合、型は「char[6]」になります。
配列型は簡単に言うとメモリ上に列挙した値を[]演算子でポインタ参照しているだけなので、仮に「"Hello"[0]」と書いていたならば「char* = char」でエラーになりますが、今回は「"Hello"」でポインタ参照していないので「char*」になります。
そのため、「char * result="Hello";」は成立します。
なぜresultのprintにアスタリスクが必要?
少し質問の意図がわからなかったので「なぜprint関数に使用しているresult変数にアスタリスクが必要ないのか?」と推測して回答します。
%sの機能は「受け取ったアドレスから「\0(※1)」までのデータを文字列として出力する」という物です。(これは調べた訳ではなく私自身の解釈ですがたぶんこんな感じ
そしてC言語の仕様として文字列リテラルの最後に「\0(※1)」が付加されることが保証されてます。
なので本コードの出力は「Hello」になります。
もしよければ「result = "Hello";」のところを「result = "Hel\0lo";」に変更して実行したり、配列型について調べてみてください。
あなたの理解の助けになると思います。
※1 正確には値0のバイト又はコード
投稿2019/07/23 10:03
総合スコア22
0
こんにちは。
int* int_ptr;
はint型へのポインタですね。同様にchar* result;
はchar型へのポインタです。
問題はint_ptrやresultが何を指すか?なのです。C言語はコロンブスの卵的な発想でポインタに2種類の使い方があります。
1つは普通に1つのint型やchar型変数へのポインタです。これは普通ですね。
もう一つはint型やchar型変数の並びへのポインタです。ポイント先が1つだけとの制約はないのです。
例えば、int_ptrは10個の要素を持つint型配列のどれかの要素を指してもよいのです。もし、先頭を指せばint_ptrの指す先はそのint型配列(の先頭)を指しているわけです。
同様にresultは{'H', 'e', 'l', 'l', 'o', 0}という6個の要素を持つchar型配列の先頭を指しています。
次に、C言語は文字列を扱う際にも上記のようにchar型配列の先頭を指すという考え方を採用しました。これだけでは致命的な問題が1つあります。文字数を表現できないのです。
そこで、null終端する(=番兵方式)ことで文字列の終わりを表現することにしているわけです。
その結果、文字列サイズ管理は処理系ではなくプログラマの仕事となりむちゃくちゃ苦労します。しかし、これは例えばメモリが数100Bytesしかないような超小型コンピュータには逆に非常にありがたい仕様です。この辺が「高級アセンブラ」とも呼ばれる所以の1つと思います。
以上のような視点で、ご質問の件を検討されると理解できるのではないかと思います。
投稿2019/07/23 01:57
総合スコア23272
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/07/23 08:19
2019/07/23 08:45
2019/07/23 14:44
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/07/22 15:50
2019/07/22 16:04
2019/07/23 02:32
2019/07/23 04:03
2019/07/23 08:04
2019/07/23 09:54 編集
2019/07/23 14:42