先頭へ戻る

ポインタ②(配列や文字列との関係性) 解答ページ | Programming Place Plus C言語編 第32章

Programming Place Plus トップページ -- C言語編 -- 第32章

先頭へ戻る

問題①

問題① 配列に文字列を代入するとき、strcpy関数を使わないといけない理由を説明してください。


たとえば、次のように記述すると、コンパイルできません。

char[] str = "abcde";
str = "xyz";

これは、式の中に単独で現れた配列が、ポインタに置き換えられるためです。 上の代入式は、結局、次のように解釈されてしまいます。

&str[0] = &"xyz"[0];

左辺側が、値を受け取れる形になっていないため、この代入文は成立しません。 そこで、代わりに strcpy関数を使って、以下のように書かなければなりません。

strcpy( str, "xyz" );

strcpy関数がしていることは、文字を1文字ずつ代入していく作業です。 具体的には、以下のようなことをしていると考えられます。

for( i = 0; i < len; ++i ){  // len はコピー元文字列 src の長さ
    str[i] = src[i];
}

これなら、ポインタとして扱われることもなく、問題なく代入が行えます。

問題②

問題② 次の4つの printf関数の出力結果をそれぞれ答えてください。

char str1[] = "abcd";
char* str2 = "abcd";

printf( "%zu\n", sizeof(str1) );
printf( "%zu\n", strlen(str1) );
printf( "%zu\n", sizeof(str2) );
printf( "%zu\n", strlen(str2) );


配列版の文字列に対する sizeof演算子は、配列全体の大きさを返します。 str1 は "abcd" なので、末尾の '\0' を含めて、大きさは 5 です。

一方、ポインタ版の文字列str2 の場合、sizeof演算子が返すのは、ポインタ変数の大きさです。 その大きさは(32ビット環境ならば) 4 になります。

一方、strlen関数の方は、配列版の str1 であっても、ポインタ版の str2 であっても、 4 を返します。 strlen関数の内部では、渡された文字列の中に '\0' が現れるまで、文字数をカウントして、その結果を返します。

問題③

問題③ strlen関数と同じことをする処理を書いてください(関数化する必要はありません)。


たとえば、次のように書けます。

#include <stddef.h>
#include <stdio.h>

int main(void)
{
    char str[] = "abcd";

    for( size_t i = 0; str[i] != '\0'; ++i ){
        // 空ループ
    }
    printf( "%u\n", i );

    return 0;
}

実行結果:

4

strlen関数は、'\0' までの文字数をカウントし、size_t型で返します。 そこで、for文などで先頭要素から始めて、'\0' が現れるまで要素を1つずつ確認していけばいいということになります。

いろいろと書き方は考えられますが、上のプログラム例では、添字i を使って要素を参照しています。 ポインタを前面に出して、以下のように書くこともできます。

#include <stddef.h>
#include <stdio.h>

int main(void)
{
    char str[] = "abcd";

    size_t len = 0;
    for( char* p = str; *p != '\0'; ++p ){
        ++len;
    }
    printf( "%u\n", len );

    return 0;
}

実行結果:

4

次のようにアドレス計算を使えば、変数len も不要になります。

#include <stdio.h>

int main(void)
{
    char str[] = "abcd";

    for( char* p = str; *p != '\0'; ++p ){
        // 空ループ
    }
    printf( "%d\n", p - str );

    return 0;
}

実行結果:

4

配列の末尾のメモリアドレス( '\0' のところのメモリアドレス)から、先頭要素のメモリアドレスを減算すれば、配列全体の要素数になります。 (ただし、この場合は ptrdiff_t型になります)。

このようにいろいろと方法は考えられますが、どれでも正解です。しかし、これまでにも何度か書いているように、分かりやすさこそ重要視するべきです。3つ目の方法は、アドレス計算まで持ち出しており、格好良く見えるかもしれませんが、これは意味なく技巧的だとも言えます。恐らく、1つ目の方法が一番分かりやすく、効率的でもあります。

問題④

問題④ 次のように定義された配列があります。

int table[] = { 0, 10, 20, 30, 40, 50, 60, 70 };

この配列table のメモリアドレスを printf関数の "%p" 変換指定子を用いて調べたところ、0x0013D684 でした。このとき、配列table の末尾の要素70 のメモリアドレスがいくつであるか答えてください。ただし、sizeof(int) は 4 であるものとします。


配列の各要素は、メモリ上に連続的に並ぶことが保証されています。sizeof(int) == 4 であるという条件から、各要素は 4バイト刻みで並んでいることになります。

配列のメモリアドレスというのは、先頭要素のメモリアドレスを意味しており、これが 0x0013D684 であれば、末尾にある 70 は、そこから 7要素分先にあるので、「0x0013D684 + 4 * 7」が、末尾の要素のメモリアドレスです。

したがって、0x0013D6A0 が正解です。



参考リンク


------------------------------------------------------------------------

更新履歴

'2018/6/1 第38章から練習問題④を移動してきた。

'2018/3/8 全面的に文章を見直し、修正を行った。

'2018/2/21 文章中の表記を統一(bit、Byte -> ビット、バイト)

'2009/11/18 新規作成。



第32章のメインページへ

C言語編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
Twitter でツイート Twitter をフォロー LINE で送る
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー