クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

C言語の配列と文字列

このレッスンのあとで: 配列をメモリとして、文字列を0で終わるバイトとして説明できる。

55分のレッスンコマンド4件コンパイラとアーキテクチャのラボ:gcc、flex、bison、RISC-V を1台の VM で

文字列は0で終わるバイトの列

C言語の文字列は、文字のコードを並べて、最後に0のバイトを置いたものです。専用の型はなく、char の配列です。

この終わりの0を忘れたり、置く場所が足りなかったりすると、バッファオーバーフローという危険な誤りになります。配列と文字列の形を知ると、その誤りの理由が見えます。

このレッスンのゴール

メモ

配列をメモリとして、文字列を0で終わるバイトとして説明できる。

重要な言葉

ことばいみれい
配列同じ型の要素が、続けて並んだものint a[4]
文字列0で終わる char の並び
ヌル終端文字列の最後に置く0のバイト\0
文字コード文字に割り当てた数'T' は84
バッファデータを入れる、決まった大きさの場所
バッファオーバーフローバッファの外に書き込んでしまう誤り

学生が入力するセルは、このプレビューでは空白です。

配列は同じ型の要素がメモリに並ぶ

int a[4] は、4バイトの要素が4つ、すき間なく並んだものです。全体は16バイトで、a[2] は先頭から8バイト先にあります。

C言語は、配列の長さを覚えていません。範囲の外を読み書きしても、止めてくれません。だから長さは、自分で持ち歩きます。

配列 int a[4] の中身

要素先頭からのバイト数
a[0]0から3
a[1]4から7
a[2]8から11
a[3]12から15

学生が入力するセルは、このプレビューでは空白です。

配列の大きさと要素の数

コマンド
printf '%s\n' 'int main(void){int a[10]; printf("%zu %zu\n", sizeof(a), sizeof(a) / sizeof(a[0])); return 0;}' | gcc -include stdio.h -x c - -o /tmp/len && /tmp/len

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

文字列は文字のコードに0を足したもの

"Tokyo" は5文字ですが、メモリでは6バイトです。最後に0のバイトが付くからです。

strlen は、0の手前までの文字数を数えるので、5を返します。sizeof は配列全体の大きさなので、6です。この2つは別のものです。

"Tokyo" の6バイト

番号文字文字コード
0T84
1o111
2k107
3y121
4o111
5終わりの印0

学生が入力するセルは、このプレビューでは空白です。

sizeof と strlen の違い

コマンド
printf '%s\n' 'int main(void){char s[] = "Tokyo"; printf("%zu %zu\n", sizeof(s), strlen(s)); return 0;}' | gcc -include stdio.h -include string.h -x c - -o /tmp/str && /tmp/str

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

6バイトの文字コードを並べる

コマンド
printf '%s\n' 'int main(void){char s[] = "Tokyo"; for (int i = 0; i < 6; i++) printf("%d ", s[i]); printf("\n"); return 0;}' | gcc -include stdio.h -x c - -o /tmp/codes && /tmp/codes

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

バッファの外に書くと、隣のデータが壊れる

4バイトの name に、abcd の4文字と、終わりの0を書くと、5バイト要ります。1バイトがあふれて、隣のメンバーに当たります。

下の Python は、メモリを12バイトのリストで真似ます。本物のC言語では、あふれたときの結果は決まっていません。コンパイラや検査の道具が、見つけて止めることもあります。

隣の変数を壊す様子を真似る

コード: python
# メモリを12バイトのリストで真似ます。0から3が name、4から7が flag です。
memory = [0] * 12
memory[4] = 1
text = "abcd"
for i in range(len(text) + 1):
    if i < len(text):
        memory[i] = ord(text[i])
    else:
        # 5番目に、終わりの0が書かれ、flag の最初のバイトを上書きします。
        memory[i] = 0
print("name の4バイト:", memory[0:4])
print("flag の最初のバイト:", memory[4])
# text を "ab" に変えて、もう一度実行しましょう。

大きさを守ってコピーする

コマンド
printf '%s\n' 'int main(void){char buf[8]; snprintf(buf, sizeof(buf), "%s", "abcd"); printf("%zu %s\n", strlen(buf), buf); return 0;}' | gcc -include stdio.h -include string.h -x c - -o /tmp/safe && /tmp/safe

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

よくある間違い

注意

文字列の長さは、sizeof で分かる、と思いがちです。sizeof は配列全体の大きさなので、終わりの0も数え、余った部分も数えます。文字数が知りたいときは strlen を使います。コピーの前には、文字数に1を足した大きさがあるか、確かめます。

さらに挑戦: 文字列をつなげる

課題が通ったら、concat(first, second, size) を書きましょう。0で終わる2つのリストをつなげ、size バイトのバッファに入れます。

入りきらないときは None を返します。そして、"ab" と "cd" を size が5と4のときに試す、自分のテストを書きましょう。

このラボとソースについて: コンパイラとアーキテクチャのラボ:gcc、flex、bison、RISC-V を1台の VM で

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。