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

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

    View Slide

  2. はじめに
    ● 想定聴衆
    ○ 「C言語は聞いたことはあるがどんなものか知らない」という人
    ○ メモリ管理を自分でしなくていいプログラミング言語を使っている人
    ■ ここ十数年で生まれた言語はだいたいそう
    ● はなすこと
    ○ 想定聴衆に向けてC言語の特徴をいくつか紹介
    ○ たくさんある特徴の中から Cを知らない人が面食らいそうなものを抽出

    View Slide

  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]);
    }
    }

    View Slide

  4. 実行結果
    ● 結果は(当然)同じ
    C
    Go
    0
    1
    2
    3
    4
    0
    1
    2
    3
    4

    View Slide

  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]);
    }
    }

    View Slide

  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]にアクセスしている
    これは何のデータ?
    怒られる

    View Slide

  7. 理由
    ● Go
    ○ 配列が要素数を示すデータを持っている (“len(a)”でアクセス可能)
    ○ 範囲外へのアクセスをすると強制終了する
    ● C
    ○ 配列は要素数を示すデータを持っていない
    ○ 配列は単にデータを並べてあるだけ
    ○ 範囲外にアクセスするとメモリ上の範囲外にあるデータにアクセス可能
    ■ 前述の範囲外アクセスだと a[]の隣にあるデータを読んでいる

    View Slide

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

    View Slide

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

    View Slide

  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
    ? ? ? ? ? ?


    プログラマから見える世界
    メモリ
    ?

    View Slide

  11. データ破壊発生
    0
    10
    20
    30
    40
    50
    aに関係ないデータが壊れる!
    a
    0 10 20 30 40
    0 10 20 30 40
    ? 50 ? ? ? ?


    プログラマから見える世界
    メモリ
    ?

    View Slide

  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

    View Slide

  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)
    }
    イマココ

    View Slide

  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)
    }
    イマココ

    View Slide

  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に変更!

    View Slide

  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でも基本的には同じ流れになる

    View Slide

  17. 実行結果
    ● おなじになる
    10, 20
    Go C
    10, 20

    View Slide

  18. GoとCにおけるポインタの違い
    ● Go
    ○ 特定の型(例ではint)の変数のアドレスをポイントできる
    ○ ポイント先の変数の変更ができるが、それ以外の操作はできない
    ● C
    ○ 特定の型(例ではint)の変数のアドレスをポイントできる
    ○ ポイントする変数の変更ができる
    ○ ポインタ変数は加減算によってポイントするデータを変更できる

    View Slide

  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がメモリ上で必ず連続している保証はないが、ここではそうなっていると仮定
    コレナニ?

    View Slide

  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);
    }
    イマココ

    View Slide

  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変数ひとつぶん増える !

    View Slide

  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);
    }
    イマココ

    View Slide

  23. Cで起きがちなポインタまわりの悲劇
    ● 誤ったポインタ加減算によって思わぬ問題が発生しうる
    ○ 想定していたものと違う変数の値を読み書きしてしまう
    ○ 不正なアドレスにアクセスしてプログラムが強制終了
    ● 上記のようなバグを悪用したセキュリティ攻撃を受ける
    ○ ブラウザ経由でサーバアプリを落とす
    ○ データを盗む
    ○ 任意のコードを実行

    View Slide

  24. わかったこと
    ● Cは良くも悪くもメモリの構造がむき出しになっている
    ● 言語レベルでのデータの保護機能があんまりない
    ○ 📝 他の言語も意図的にデータ保護機能を切ったりできることもある
    ● もっといっぱいあるが、多すぎるので省略

    View Slide

  25. いまどきCを使いたい場面
    ● どうしても高速、軽量なプログラムを作りたい場合
    ○ データ保護などの便利機能のためのデータやロジックはなくていい
    ○ キャッシュメモリなど、ハードウェアを意識したプログラムを作ってもよい
    ○ ユーザはいまどきの言語を使い、処理系を作る人は Cを使って処理系を書いたり
    ■ 例: Python処理系はCで書かれている
    ● カーネルやデバイスドライバ
    ○ 「特定のメモリアドレスのデータ」を操作しなければならない場面がままある
    ○ MMIOという特定メモリアドレスにアクセスするとデバイスを操作できるというしくみがある
    ○ 📝 最近は状況が変わりつつある
    ■ Linuxはv6.1からRustで一部コードを書けるようになる見込み

    View Slide

  26. おわりに
    ● C言語はアセンブリ言語と高級言語の間くらいにあるプログラミング言語
    ● つらいところ
    ○ 普通に使っていてもメモリの概念がむき出しになっている
    ○ バグを仕込みやすいし、仕込んだ時にデバッグが難しい
    ● いいところ
    ○ (アセンブリ言語よりは )人間に理解しやすい文法で高速なプログラムを書ける
    ○ メモリを意識せざるをえないデバイスドライバなどを書ける
    ● 📝 わたし個人の思い
    ○ 「いいところ」が嬉しい場合は Cを使う
    ○ それ以外の場合はわざわざ使わなくていい (速度が重要じゃないとか )

    View Slide