一回も披露したことのない豆知識

#include <stdio.h>
int main(void) {

char str[] = { "abcde" };

for (char* p = str; *p; ++p)
{
++(*p);
printf("%s\n", &(*p));
}

}

結果は
bbcde
ccde
dde
ee
f


ただなんでこのような結果になるのか自分で書いて置きながらわからないので、自分なりに解説すると同時に疑問点を上げていきます。

まずchar* p = strより、strに入っている文字列abcdeをchar* p により、ポインタpの指すアドレスの番号ではなく、アドレスに入っている文字列abcde(文字コード)が渡される。そして、for文の++pにより文字列のアドレスに入っている文字コードの数値が+1されるのでポインタpが先頭のアドレスから表す文字列はbcdeとなる、それから*pには文字コードが一つしか入らないようなので、「b」だけが入る、そして、なぜかデバッグの結果より[0]に*pに代入されたbが代入され、 strの中身の数列はbcdeとなっているので、なんで[6]にbが代入されないのかわからないですが、*pに代入されたbが代入されたbが[0]として、bbcdeとなりました。



疑問1 なぜ*pには文字コードが一つしか入らないのか。

疑問2 なぜ[0]に*pに代入されたbが代入されたのか、個人的には文字列は最後に表示するためてっきりbcdebだと思っていました。

質問者からの補足コメント

  • なるほどありがとうございます。
    ちなみに、
    なぜ&(*p)は*をつけないとならないのですか?
    詳しく知りたいです。
    どうせポインタと同じなら&(p)でも良いのではないでしょうか?
    また、&*(str+i)に関しても、ポインタならば
    *はいらないのではないでしょうか?

      補足日時:2021/02/16 14:29

A 回答 (6件)

#3です。



補足に対して。
>なぜ&(*p)は*をつけないとならないのですか?

C言語で変数を宣言すると、その変数の大きさに合わせたメモリ領域が確保されます。
これはポインタ変数といえどもまったく同様です。

char* p;
と宣言するとポインタ変数分のメモリ領域が確保され、それがpという名前に紐づけられます。
メモリ上に領域があるということは、その領域を示すアドレスが存在する、ということです。
そのアドレスが&pの意味するものです。確かに&pもポインタ型ではあるのですが、さしているのはpそのものであり、pが指し示しているものとは別物なのです。

例えば、
char* p;
char str[]="abcde";
と宣言するとp,str[0]~str[5]の領域が確保されます。
それぞれのアドレスを
p:50
str[0]:100
str[1]:101
str[2]:102
str[3]:103
str[4]:104
str[5]:105
としましょう。str[0]~str[5]は必ず連続して領域が確保されます。

p=str;
とすると、文字型へのポインタpにstr[0]のアドレスが代入されます。
この時点でメモリ上の50番地に"100"というデータが書き込まれます。

ではこの段階で&pと&(*p)がどのような値となるか考えてみましょう。
&pはpというポインタ変数が収められているアドレスです。つまり、&pの値は50です。
&(*p)とはポインタpに格納されているアドレスにおいてあるデータが*pですので、*pのアドレスとはそのデータのおいてあるアドレスを意味します。
この段階でpの中には100というデータが入っているので、*pはアドレス100に入っている'a'を意味します。
&(*p)は'a'というデータがおいてあるアドレス、つまりは100です。これはpに格納されているデータと同一です。
    • good
    • 1
この回答へのお礼

すごいわかりやすいです!
是非先生になってほしいくらいです。
良ければこちらも答えて頂けるとありがたいです。https://teratail.com/questions/322870

お礼日時:2021/02/16 18:18

No.4の補足



Single byte と Double byte の文字の違いによるものかどうかご確認ください。

"?" は "?"と同じではありませんし、":"は":"と同じではありません。
「#include <stdio.h> i」の回答画像5
    • good
    • 0

GCC環境でも期待通りの出力が可能です。

locale設定を確認してください。

$ cat 322763.c
#include <stdio.h>
#include <string.h>

int main(void) {
char str[] = "str == NULL ? \"(NULL)\" : str";
char* p, * q;
int ch;
p = str;
for (;;) {
for (q = p; !(*q == '?' || *q == ':' || *q == 0); q++);
ch = *q;
*q = 0;
printf("|%s|\n", p);
if (ch == 0) break;
p = q + 1;
}
}
$ gcc -o 322763 322763.c
$ ./322763
|str == NULL |
| "(NULL)" |
| str|
$
$ locale
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=
$
「#include <stdio.h> i」の回答画像4
    • good
    • 0

char* p;


と宣言するとpが示すのは文字1個を示すデータのアドレスにすぎません。
pが文字列を表すわけではありません。
*pが表すのは文字1個だけです。
そもそもC言語には文字列というデータ型はありません。連続したメモリ領域に文字が連続して格納され\0で終端されているにすぎません。

char str[]="abcde";
と宣言するとメモリ上に少なくとも6バイトの連続した領域が取られます。
先頭の'a'のアドレスを100とすると
100:'a'
101:'b'
102:'c'
103:'d'
104:'e'
105:'\0'
のアドレスにデータが配置されます。必ず連続に配置される、という点と終端が\0になっている、という点が重要です。

p=str;
と代入するとpにstr[]が表す配列の先頭のデータのアドレスが代入されます。
つまり、'a'のアドレス100がpに代入されます。
質問者はこの時点でなぜかpに文字列"abcde"が渡されていると思い込んでいますが、pに代入されているのはあくまで'a'のアドレスにすぎません。
pには、指示しているものが文字列の先頭である、という情報すら渡されていません。

for(;;)の中の*pは*pが0,文字としては'\0'であればループから抜けるということですがここでは*pは'a'となっており0ではないため{}の中を実行します。

++(*p);
ここではpが示すアドレスにあるデータ'a'を1増やします。'a'の文字コードを1増やすと'b'の文字コードになります。これでaの文字がbに変化します。
あとは変化しません。
100:'b'
101:'b'
102:'c'
103:'d'
104:'e'
105:'\0'
pの値 100

printf("%s\n", &(*p));
何か馬鹿なことをしています。何ですか、&(*p) 単にpでいいのに何やってるの?
pがアドレスを示し、*pがそのアドレスに入っているデータを表す。&をつけてもアドレスを示すだけでpの値が得られるだけです。

printf("%s\n", p);
がやっていることが何かを確認しておきましょう。
printf()関数は初めの引数にあるエスケープ文字%sから、第二引数pのさすアドレスから連続して文字を表示します。そして終端文字\0が出てくるまでそれを続けます。
つまり、pの値100のアドレスにある文字'b',101のアドレスにある文字'b',102のアドレスにある文字'c',...,104のアドレスにある文字'e'をバッファに収め、次のアドレス105に終端文字\0を見つけるとバッファにある文字を画面に出力します。
さらに\n,つまりは改行を表示します。
この時の出力は
bbcde改行
100:'b'
101:'b'
102:'c'
103:'d'
104:'e'
105:'\0'
pの値 100

となります。

ここでfor()次のブロックの1回目が終了、for(;;)の最後の++pを実行します。
++p;
単にpを1個分増やします。今回の場合、文字1個につきメモリ領域1個分ですので100が1増えて101になります。
100:'b'
101:'b'
102:'c'
103:'d'
104:'e'
105:'\0'
pの値 101

++(*p);
この段階でpの値は101です。101のアドレスにある'b'の文字コードを1増やすと'c'の文字コードになります。これで101のアドレスにあるbの文字がcに変化します。
あとは変化しません。
100:'b'
101:'c'
102:'c'
103:'d'
104:'e'
105:'\0'
pの値 101

printf("%s\n", p);
pの値101のアドレスにある文字'c',102のアドレスにある文字'c',...,104のアドレスにある文字'e'をバッファに収め、次のアドレス105に終端文字\0を見つけるとバッファにある文字を画面に出力します。
さらに\n,つまりは改行を表示します。
この時の出力は
ccde改行
100:'b'
101:'c'
102:'c'
103:'d'
104:'e'
105:'\0'
pの値 101

という風になります。
なお、この間にstrの値は常に100で不変です。
また、str[]で表される文字列の大きさも変わりません。
この段階でstr[]に入力されているデータは"bccde"と同一です。
printf("%s\n"",p)がcから表示しているのはpの値が101だからです。

これ以降については自分で考えましょう。

質問者の疑問について
疑問1 なぜ*pには文字コードが一つしか入らないのか。

pが文字1個分の変数へのポインタとして宣言されているからです。
繰り返し言いますが、C言語には文字列型、という型はありません。
pに入っているデータは単なるアドレスだけで、指し示す先のデータのサイズや型はp自体はデータとしては持っていません。(*pの型はコンパイラが認識している)

疑問2
意味不明 [0]とか何?
    • good
    • 0
この回答へのお礼

なるほどありがとうございます。
ちなみに、
なぜ&(*p)は*をつけないとならないのですか?
詳しく知りたいです。
どうせポインタと同じなら&(p)でも良いのではないでしょうか?
また、&*(str+i)に関しても、ポインタならば
*はいらないのではないでしょうか?

お礼日時:2021/02/16 14:29

ご質問にお答えすると、



- Cの演算子は文字を個別に操作しますが、文字列全体に対しては操作しません。そのために、文字列を操作するには関数を使う必要があります。

- printf("%s\n", &(*p)) の %s はポインタの指し示すアドレスのメモリー場所の内容だけでなく、印刷のために隣接する内容も取る関数です。


str: (Type = char *- 割り当てられたメモリー場所のアドレスを格納するメモリー場所である)
[a][b][c][d][e] <- 内容
| | |
| | |__アドレス (1004)
| |__アドレス (1001)
|__アドレス (1000) = strの値

* 1000は例である

p: (Type = char * - アドレスを格納するメモリー場所である)
[1000] = pの値
|
|__アドレス (8120) = &pの値

* 8120は例である


変数宣言中に :

char *p <--- pは、アドレスを格納するメモリー場所である


コード中に:

&(*p) (Type = char * - アドレス)
^ ^^
| | |___アドレス
| |___内容
|___アドレス

&pの値は アドレス (8120) である

pの値は アドレス(1000) である

*p <--- *p は、pで指し示すアドレス (1000) のメモリー場所の内容 ('a') である
*P の値は 'a' である。もちろん、文字(char) ポインタの内容が文字である

&(*p) <--- *pの内容のメモリー場所のアドレスである.
&(*p) の値は アドレス(1000)である

printf("%s\n", &(*p))
%s - char *を受け取り、それの指し示すアドレス (1000) のメモリー場所の内容をNULL終端の文字列 ("abcde\0") として印刷します
「#include <stdio.h> i」の回答画像2
    • good
    • 0
この回答へのお礼

すんごいわかりやすいです。
どうか
teratailというサイトのhttps://teratail.com/questions/322763にもお答えして頂けないでしょうか?

お礼日時:2021/02/16 11:58

Dry runをご覧ください。



Loop 1 :
p -> str[0]
*p = a
str = "abcde"
++(*p) = a -> b
str => bbcde
&(*p) = bbcde

Loop 2 :
p -> str[1]
*p = b
str = "bbcde"
++(*p) = b -> c
str => bccde
&(*p) = ccde

Loop 3 :
p -> str[2]
*p = c
str = "bccde"
++(*p) = c -> d
str => bcdde
&(*p) = dde

Loop 4 :
p -> str[3]
*p = d
str = "bcdde"
++(*p) = d -> e
str => bcdee
&(*p) = ee

Loop 5 :
p -> str[4]
*p = e
str = "bcdee"
++(*p) = e -> f
str => bcdef
&(*p) = f
    • good
    • 1
この回答へのお礼

ありがとうございます
ちなみに、なぜ*pには文字コードが一つしか入らないのか。

お礼日時:2021/02/16 04:55

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!