わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

論証には適切な例を添えましょう

それでは余談です.
文字列リテラルは「配列」か「ポインタ」か,という論争があります.結論から言うと,文字列リテラルは「ポインタ」です.
プログラムを動かして,そのことを確認しましょう.例えば,こう書くことができます.

#include <stdio.h>

int main(void)
{
  printf("%u\n", sizeof(char *));
  printf("%u\n", sizeof("abc"));

  return 0;
}

「%u」というのは,見慣れないかもしれませんが,第2回の授業で,説明していますよ.対象を,unsignedすなわち符号なしとみて,10進整数値として出力します.
さて…printfの一つ目は,char *型,すなわちポインタ型は何バイトかを教えてくれます.2番目は,"abc"という文字列リテラルが,何バイトになるかです.
実行してみると,「4」「4」となりました.ここから,"abc"と書いた文字列リテラルが,ポインタと同じバイト数であることが分かります.したがって状況証拠ながら,文字列リテラルはポインタであることが確かめられました.
論証には適切な例を添えましょう,というお話でした.


あの,えっとですねえ.
これで終わったら,「余談」などしません.今のストーリーには,欠陥があります.そして,そもそも,文字列リテラルは,ポインタではなく配列なのです.
どうすればいいのかというと,先ほどの"abc"というのを,そうですね,"abcdef"に変更しましょうか.

#include <stdio.h>

int main(void)
{
  printf("%u\n", sizeof(char *));
  printf("%u\n", sizeof("abcdef"));

  return 0;
}

そうしてコンパイル・実行すると,今度は…「4」と「7」が出力されます.
char *が何バイトかというと,4バイトです.
"abcdef"という文字列リテラルが,何バイトになるのかは,今出たとおり7バイトです.ということで,"abcdef"はポインタではありません.
なぜ,こうなるのかというと…a,b,c,d,e,fの6バイトに,ナル文字の1バイトを付け加えれば,いいのです.
さっきは,sizeofの"abc"が「4」でしたね.これも,a,b,cの3バイトに,ナル文字の1バイトを付け加えれば,4バイトとなります."abc"も,"abcdef"と同じように考えると,実はポインタではないのです.
「文字列はナル文字で終わるcharの配列」というのを思い出してください.文字列リテラルも,ナル文字で終わるcharの配列なのです.ただし配列変数に格納されているものではないので,その中身を書き換えることはできませんけどね.
改めて言います.今回の余談は,論証には適切な例を添えましょう,というお話でした.

もう少し手の込んだ検証コード

#include <stdio.h>

#define printf_u(v) printf(#v " = %u\n", v)

int main(void)
{
  char *s = "abcdef";

  printf_u(sizeof(char *));
  printf_u(sizeof("abc"));
  printf_u(sizeof("abcdef"));
  printf_u(sizeof(s));
  printf_u(sizeof(""));

  return 0;
}

関数形式マクロで「#識別子」を使うと,プリプロセッサがそれを置換対象の文字列リテラルにしてくれること,また文字列リテラルは連結されることを活用しています.「printf_u(sizeof(char *));」は,「printf("sizeof(char *)" " = %u\n", sizeof(char *));」を経て,「printf("sizeof(char *) = %u\n", sizeof(char *));」と同じ意味になります.
出力は,次のようになります.

sizeof(char *) = 4
sizeof("abc") = 4
sizeof("abcdef") = 7
sizeof(s) = 4
sizeof("") = 1

空文字列のサイズは,0ではなく1です.ナル文字のみからなるcharの配列と考えればいいのです.
型や値をいろいろ変更し,出力を見ることで,何を指し示すかにかかわらず,「sizeof(ポインタ変数)」は「sizeof(ポインタ型)」と等しいことも確認できます.

なにこれ

12月26日の授業の余談でした.
試験では,文字列のメモリ表現に対する理解を見るため,次の出題をしました.

  • 文字列を指し示すポインタsについて,s[strlen(s)]は,Xと等しい.Xには次のどれが当てはまるか.
    1. '\0'
    2. -1
    3. 文字列の最後の文字
    4. 文字列の長さ

正解は「'\0'」です.筆記試験なので,学生はプログラムを書いて確かめるというわけには,いきませんが,図にすれば確かめられます*1

*1:初期値はいずれも文字列リテラルとしましたが,これらの例では,その先頭アドレスがsに代入されます.なお,配列による(書き換え可能な)文字列を指し示したり,文字列の途中の位置を指し示したりしても,s[strlen(s)] == '\0'が成立します.