🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

Q&A

解決済

6回答

11678閲覧

charのポインタ変数に*なしで文字列を代入できる理由が知りたい

Leader731

総合スコア20

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

1グッド

3クリップ

投稿2019/07/22 15:18

編集2019/07/22 15:25

###知りたいこと
下記のコードでなぜコンパイルが通るのかが分かりません

###問題のコード

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

meguko2002👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答6

0

"Hello"というのは、('\0'終端の)文字列「Hello」の先頭アドレスを表します。
アドレスなので、ポインタに代入できます。

Cでは、文字列自体を=で何かに代入することは出来ません。それ以前に、文字列自体を直接扱えません。
文字列を処理するには、1文字ずつ分解して(文字列じゃなく)文字として処理するか、文字列の先頭アドレスを関数に渡して処理してもらうか、どちらかになります。

printf%sは、アドレスを受け取って、そのアドレスから'\0'があるまで文字列が入っているとして表示します。

'A'"A"の違いは理解できていますか?

投稿2019/07/22 15:37

otn

総合スコア85890

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Leader731

2019/07/22 15:50

ご回答ありがとうございます。 'A'は文字リテラルで、"A"は文字列という理解です。 そして、文字リテラルというのは一文字を指し、文字列は文字リテラルの集合体という理解です。 C言語だと文字列は扱えないので文字列を文字リテラルに分解し、文字リテラル単位でアドレスを参照するということですね。
otn

2019/07/22 16:04

そうですね。 > 文字列は文字リテラルの集合体 で、文字列リテラルは、文字の集合体の先頭アドレスです。
rubato6809

2019/07/23 02:32

> C言語だと文字列は扱えない そう? C言語は文字列を先頭アドレスで扱ってますが。
SaitoAtsushi

2019/07/23 04:03

言語仕様のルールで厳密に言うと文字列 "Hello" の型は配列型の一種 const char[6] で、これ自体は値として扱えません。 いくつかの条件が揃っている場合に限って「暗黙の型変換」で配列の先頭要素を指すポインタ (型で言えば const char*) になるというルールによってポインタになります。 例外的なルールに基づくものなのでしばしば初心者が躓くのは当然かもしれません。 余談ですが「配列の先頭要素を指すポインタ」とは別に「配列を指すポインタ」も存在し、配列に & を付けることで得ることは出来ます。 const char (*c)[6] = &"Hello";
Leader731

2019/07/23 08:04

ご回答有り難うございます。 result = &"Hello";ではなくresult = "Hello";なんだろうという点も実は気になってました。 「配列の先頭要素を指すポインタ」とは別に「配列を指すポインタ」は別なんですね。 配列を指すポインタというのは、配列の枠組みに対するアドレスが存在し、要素は特に指定せずに枠組みのアドレスを参照しているというイメージで合っていますでしょうか?
SaitoAtsushi

2019/07/23 09:54 編集

はい。 配列を指すポインタは複数の要素をまとめた「配列全体」を指しています。 配列のアドレスというのはそれを構成するバイト列の先頭ということですので実態としては配列の先頭要素のポインタと同じところを指しているのですが、「指している先が何であるか」が違うわけです。
Leader731

2019/07/23 14:42

疑問が解消されました!分かりやすいご説明ありがとうございます。
guest

0

cの文字列はchar(文字)の配列です

resultにはアドレスが入るのでは?

全くそのとおりで
質問のコードは"Hello"の先頭アドレスを代入してます

投稿2019/07/22 15:38

ozwk

総合スコア13551

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Leader731

2019/07/22 15:58

ご回答ありがとうございます。 charが配列であり、Helloの先頭アドレスということは、 Hの先頭アドレスということですよね。 となると、charを参照をした場合 Hの先頭アドレス→Hの第二アドレス→Hの第三アドレス→・・・ →eの先頭アドレス→eの第二アドレス→eの第三アドレス→・・・ →lの先頭アドレス→・・・ →・・・ という風に紐付いたメモリのアドレスが順々に参照されていくイメージで合っていますか? 参照 https://oshiete.goo.ne.jp/qa/2947016.html
rubato6809

2019/07/23 02:24

へんなたどり方をしてる気がする。 "Hello" の先頭アドレスには 'H' (0x48 或いは 72) という値があります。 次の番地に 'e' (0x65 或いは 101)という値がある、 その次の番地に 'l' (0x6C 或いは 108)という値がある・・・ 最後に '\0' つまり0という値が書かれたメモリがあって、文字列の最後を示している。
Leader731

2019/07/23 08:05

ご回答ありがとうございます。 納得しました。 'H'のアドレスを参照したら後は順々に参照していって'\0'を発見したら処理を終えるという構造なんですね。
guest

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

cateye

総合スコア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
rubato6809

総合スコア1382

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Leader731

2019/07/23 15:14

ご回答ありがとうございます。 >ちなみに、パソコンのCPUはリトルエンディアンなので、0x04, 0xA0, 0x06, 0x00 という順序で格納されます。 リトルエンディアンという言葉を初めてお聞きしました。分割して最下位のバイトから上位に向けて各メモリに格納してく方式なんですね。 http://e-words.jp/w/%E3%83%AA%E3%83%88%E3%83%AB%E3%82%A8%E3%83%B3%E3%83%87%E3%82%A3%E3%82%A2%E3%83%B3.html 図を使ってご説明していただいた点と、「仮にこういう書き方をすると裏ではどんな処理が行われるのか」といったことを具体的に解説していだいた点(printf("%s", *result);を実行した場合は72が格納されるなど)を踏まえ、恐縮ながらベストアンサーにさせていただきます。 お忙しい中、図を作ってご丁寧にご解説いただきありがとうございました。
rubato6809

2019/07/24 00:34

評価ありがとうございます。 メモリアドレスを確認する例を追加しました。
guest

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

kamitani

総合スコア22

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Leader731

2019/07/23 14:46

ありがとうございます。お陰様でかなり疑問が解消されてきました。
guest

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

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Leader731

2019/07/23 08:19

ご回答有り難うございます。 >同様にresultは{'H', 'e', 'l', 'l', 'o', 0}という6個の要素を持つchar型配列の先頭を指しています。 この場合、配列の先頭というのはresult[0]つまり、'H'のアドレスを意味している訳ですね。 そして、'H'のアドレスを参照したら後は順々に参照していって'\0'を発見したら処理を終えるという構造なんですね。 C言語の背景からご丁寧にご説明いただいきありがとうございます。お陰様でかなり理解が深まってきました。しかし、アドレスをプログラマが指定できるようになることで、なぜメモリが節約できるのかが未だに理解できていません。自動で使用するメモリを割り当ててくれる他の言語でも使用するメモリの容量は同じのような気がしてしまいます…。 この部分を一度調べてみます。
Chironian

2019/07/23 08:45

自動でメモリを割り当てるということは事前にメモリ・サイズを決定できないと言う意味です。 メモリを確保出来なかった時に落ちて良ければ問題ないのですが、例えば冷蔵庫が夏の暑い時に落ちては困ると思います。 なので、メモリが少ないシステムでそんな贅沢な使い方はできないのです。通常は固定長メモリで使い、事前に最大文字数を決め打ちにして使うことでメモリ不足によるプログラムの異常終了を回避するのです。
Leader731

2019/07/23 14:44

ありがとうございます。かなり理解が深まってきました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問