Upgrade to Pro — share decks privately, control downloads, hide ads and more …

C言語を知らない人がびっくりしそうなC言語の特徴

 C言語を知らない人がびっくりしそうなC言語の特徴

以下動画のテキストです。
https://youtu.be/nTHqvKnCyLc

Satoru Takeuchi

October 01, 2022
Tweet

More Decks by Satoru Takeuchi

Other Decks in Technology

Transcript

  1. 配列の範囲外にアクセスすると… • C Go func main() { a := []int{0,

    1, 2, 3, 4} for i := 0; i < len(a)+1; i++ { fmt.Println(a[i]) } } void main(void) { int i; int a[5] = {0, 1, 2, 3, 4}; for (i=0;i<6;i++) { printf("%d\n", a[i]); } }
  2. 実行結果 • ちがう C Go 0 … 4 panic: runtime

    error: index out of range [5] with length 5 goroutine 1 [running]: main.main() /home/sat/src/youtube-sample/053- surprising-c/outofrange/outofrange .go:10 +0xcb 0 1 2 3 4 0 未定義のa[5]にアクセスしている これは何のデータ? 怒られる
  3. 理由 • Go ◦ 配列が要素数を示すデータを持っている (“len(a)”でアクセス可能) ◦ 範囲外へのアクセスをすると強制終了する • C

    ◦ 配列は要素数を示すデータを持っていない ◦ 配列は単にデータを並べてあるだけ ◦ 範囲外にアクセスするとメモリ上の範囲外にあるデータにアクセス可能 ▪ 前述の範囲外アクセスだと a[]の隣にあるデータを読んでいる
  4. 変数とメモリとの関係(Go) a プログラマから見える世界 メモリ 0 1 2 3 4 0

    1 2 3 4 ? ? ? ? ? ? ? ? ? ? a変数によるアクセス可能 … … a変数によるアクセス不可 a変数によるアクセス不可
  5. 変数とメモリとの関係(C) a プログラマから見える世界 メモリ 0 1 2 3 4 0

    1 2 3 4 ? ? ? ? ? 0 ? ? ? ? a変数によるアクセス可能 … … さっき見えてしまったところ
  6. 配列の範囲外に書くとどうなる? void main(void) { int i; int a[5] = {0,

    1, 2, 3, 4}; for (i=0;i<6;i++) { a[i] = i * 10; } for (i=0;i<6;i++) { printf("%d\n", a[i]); } } a 0 1 2 3 4 0 1 2 3 4 ? ? ? ? ? ? … … プログラマから見える世界 メモリ ?
  7. データ破壊発生 0 10 20 30 40 50 aに関係ないデータが壊れる! a 0

    10 20 30 40 0 10 20 30 40 ? 50 ? ? ? ? … … プログラマから見える世界 メモリ ?
  8. ポインタ • 二つの変数a, bを定義して、それぞれの値をポインタ経由で書き換え void main(int argc, char *argv[]) {

    int a = 1, b = 2; int *p = &a; *p = 10; p = &b; *p = 20; printf("%d, %d\n", a, b); } Go func main() { a := 1 b := 2 p := &a *p = 10 p = &b *p = 20 fmt.Printf("%d, %d\n", a, b) } C
  9. ポインタの動作イメージ a プログラマから見える世界 メモリ 1 1 1 2 ? …

    … b 2 p ? func main() { a := 1 b := 2 p := &a *p = 10 p = &b *p = 20 fmt.Printf("%d, %d\n", a, b) } イマココ
  10. ポインタの動作イメージ a プログラマから見える世界 メモリ 10 1 10 2 ? …

    … b 2 p ? func main() { a := 1 b := 2 p := &a *p = 10 p = &b *p = 20 fmt.Printf("%d, %d\n", a, b) } イマココ
  11. ポインタの動作イメージ a プログラマから見える世界 メモリ 10 1 10 2 ? …

    … b 2 p ? func main() { a := 1 b := 2 p := &a *p = 10 p = &b *p = 20 fmt.Printf("%d, %d\n", a, b) } イマココ ポイント先をaからbに変更!
  12. ポインタの動作イメージ a プログラマから見える世界 メモリ 10 1 10 20 ? …

    … b 20 p ? func main() { a := 1 b := 2 p := &a *p = 10 p = &b *p = 20 fmt.Printf("%d, %d\n", a, b) } イマココ * Cでも基本的には同じ流れになる
  13. GoとCにおけるポインタの違い • Go ◦ 特定の型(例ではint)の変数のアドレスをポイントできる ◦ ポイント先の変数の変更ができるが、それ以外の操作はできない • C ◦

    特定の型(例ではint)の変数のアドレスをポイントできる ◦ ポイントする変数の変更ができる ◦ ポインタ変数は加減算によってポイントするデータを変更できる
  14. ポインタ加算の動作イメージ a プログラマから見える世界 メモリ 1 1 1 2 ? …

    … b 2 p ? void main(int argc, char *argv[]) { int a = 1, b = 2; int *p = &a; *p = 10; p++; *p = 20; printf("%d, %d\n", a, b); } イマココ • aとbがメモリ上で必ず連続している保証はないが、ここではそうなっていると仮定 コレナニ?
  15. ポインタ加算の動作イメージ a プログラマから見える世界 メモリ 10 1 10 2 ? …

    … b 2 p ? void main(int argc, char *argv[]) { int a = 1, b = 2; int *p = &a; *p = 10; p++; *p = 20; printf("%d, %d\n", a, b); } イマココ
  16. ポインタ加算の動作イメージ a プログラマから見える世界 メモリ 10 1 10 2 ? …

    … b 2 p ? void main(int argc, char *argv[]) { int a = 1, b = 2; int *p = &a; *p = 10; p++; *p = 20; printf("%d, %d\n", a, b); } イマココ アドレスがint変数ひとつぶん増える !
  17. ポインタ加算の動作イメージ a プログラマから見える世界 メモリ 10 1 10 20 ? …

    … b 20 p ? void main(int argc, char *argv[]) { int a = 1, b = 2; int *p = &a; *p = 10; p++; *p = 20; printf("%d, %d\n", a, b); } イマココ
  18. いまどきCを使いたい場面 • どうしても高速、軽量なプログラムを作りたい場合 ◦ データ保護などの便利機能のためのデータやロジックはなくていい ◦ キャッシュメモリなど、ハードウェアを意識したプログラムを作ってもよい ◦ ユーザはいまどきの言語を使い、処理系を作る人は Cを使って処理系を書いたり

    ▪ 例: Python処理系はCで書かれている • カーネルやデバイスドライバ ◦ 「特定のメモリアドレスのデータ」を操作しなければならない場面がままある ◦ MMIOという特定メモリアドレスにアクセスするとデバイスを操作できるというしくみがある ◦ 📝 最近は状況が変わりつつある ▪ Linuxはv6.1からRustで一部コードを書けるようになる見込み
  19. おわりに • C言語はアセンブリ言語と高級言語の間くらいにあるプログラミング言語 • つらいところ ◦ 普通に使っていてもメモリの概念がむき出しになっている ◦ バグを仕込みやすいし、仕込んだ時にデバッグが難しい •

    いいところ ◦ (アセンブリ言語よりは )人間に理解しやすい文法で高速なプログラムを書ける ◦ メモリを意識せざるをえないデバイスドライバなどを書ける • 📝 わたし個人の思い ◦ 「いいところ」が嬉しい場合は Cを使う ◦ それ以外の場合はわざわざ使わなくていい (速度が重要じゃないとか )