$30 off During Our Annual Pro Sale. View Details »

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

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

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

Satoru Takeuchi
PRO

October 01, 2022
Tweet

More Decks by Satoru Takeuchi

Other Decks in Technology

Transcript

  1. C言語を知らない人が びっくりしそうな C言語の特徴 Sep. 30th, 2022 Satoru Takeuchi twitter: satoru_takeuchi

  2. はじめに • 想定聴衆 ◦ 「C言語は聞いたことはあるがどんなものか知らない」という人 ◦ メモリ管理を自分でしなくていいプログラミング言語を使っている人 ▪ ここ十数年で生まれた言語はだいたいそう •

    はなすこと ◦ 想定聴衆に向けてC言語の特徴をいくつか紹介 ◦ たくさんある特徴の中から Cを知らない人が面食らいそうなものを抽出
  3. C言語の概要 • C言語はアセンブリ言語と高級言語の間くらいにあるプログラミング言語 • (アセンブリ言語よりは)可読性が高いコードで高速なプログラムを書ける • 単純なコードだとあたらしめの言語と見かけ上はそんなに変わらない • 例: 配列の要素を1行に1つ出力するプログラム

    C Go func main() { a := []int{0, 1, 2, 3, 4} for i := 0; i < len(a); i++ { fmt.Println(a[i]) } } void main(void) { int i; int a[5] = {0, 1, 2, 3, 4}; for (i=0;i<5;i++) { printf("%d\n", a[i]); } }
  4. 実行結果 • 結果は(当然)同じ C Go 0 1 2 3 4

    0 1 2 3 4
  5. 配列の範囲外にアクセスすると… • 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]); } }
  6. 実行結果 • ちがう 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]にアクセスしている これは何のデータ? 怒られる
  7. 理由 • Go ◦ 配列が要素数を示すデータを持っている (“len(a)”でアクセス可能) ◦ 範囲外へのアクセスをすると強制終了する • C

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

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

    1 2 3 4 ? ? ? ? ? 0 ? ? ? ? a変数によるアクセス可能 … … さっき見えてしまったところ
  10. 配列の範囲外に書くとどうなる? 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 ? ? ? ? ? ? … … プログラマから見える世界 メモリ ?
  11. データ破壊発生 0 10 20 30 40 50 aに関係ないデータが壊れる! a 0

    10 20 30 40 0 10 20 30 40 ? 50 ? ? ? ? … … プログラマから見える世界 メモリ ?
  12. ポインタ • 二つの変数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
  13. ポインタの動作イメージ 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) } イマココ
  14. ポインタの動作イメージ 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) } イマココ
  15. ポインタの動作イメージ 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に変更!
  16. ポインタの動作イメージ 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でも基本的には同じ流れになる
  17. 実行結果 • おなじになる 10, 20 Go C 10, 20

  18. GoとCにおけるポインタの違い • Go ◦ 特定の型(例ではint)の変数のアドレスをポイントできる ◦ ポイント先の変数の変更ができるが、それ以外の操作はできない • C ◦

    特定の型(例ではint)の変数のアドレスをポイントできる ◦ ポイントする変数の変更ができる ◦ ポインタ変数は加減算によってポイントするデータを変更できる
  19. ポインタ加算の動作イメージ 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がメモリ上で必ず連続している保証はないが、ここではそうなっていると仮定 コレナニ?
  20. ポインタ加算の動作イメージ 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); } イマココ
  21. ポインタ加算の動作イメージ 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変数ひとつぶん増える !
  22. ポインタ加算の動作イメージ 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); } イマココ
  23. Cで起きがちなポインタまわりの悲劇 • 誤ったポインタ加減算によって思わぬ問題が発生しうる ◦ 想定していたものと違う変数の値を読み書きしてしまう ◦ 不正なアドレスにアクセスしてプログラムが強制終了 • 上記のようなバグを悪用したセキュリティ攻撃を受ける ◦

    ブラウザ経由でサーバアプリを落とす ◦ データを盗む ◦ 任意のコードを実行
  24. わかったこと • Cは良くも悪くもメモリの構造がむき出しになっている • 言語レベルでのデータの保護機能があんまりない ◦ 📝 他の言語も意図的にデータ保護機能を切ったりできることもある • もっといっぱいあるが、多すぎるので省略

  25. いまどきCを使いたい場面 • どうしても高速、軽量なプログラムを作りたい場合 ◦ データ保護などの便利機能のためのデータやロジックはなくていい ◦ キャッシュメモリなど、ハードウェアを意識したプログラムを作ってもよい ◦ ユーザはいまどきの言語を使い、処理系を作る人は Cを使って処理系を書いたり

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

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