Strings of Life

PHP/Phalcon/MySQL/JavaScript/RegExp/Ruby/Perl/ActionScript

タグ:C



Cプログラムが実行可能な形式になるまでの流れ(ビルドの流れ)
  1. Cソースコードを作成する
  2. プリプロセス(プリプロセッサがソースコードを書き換える)
  3. コンパイル(Cのソースコードからアセンブラのコードを作る)
  4. アセンブル(アセンブラのコードからマシン語のコードを作る)
  5. リンク(作成したコードとライブラリのコードを結びつける)

プリプロセッサの働き

  • 特定の箇所に特定のファイルを挿入する(#include)
  • 特定の文字列を特定の文字列で置換する(#define)
  • 条件に合わせて、特定の行を削除又は挿入する(#ifdef #else #end)


クロスコンパイルとは:開発マシンとターゲットマシンが異なる場合をいう(Windowsで開発して、家電で動かす、等)

構造体

  • structは、一連の他のデータ型から作成したデータ型である
  • structは固定長である
  • structフィールドは、.<field_name>という構文を使って名前でアクセスする
  • structフィールドは、コードの順序と同じ順序でメモリに格納される
  • structは入れ子にできる
  • typedefはデータ型のエイリアスを作成する
  • structでtypedefを使う場合は、structの名前を省略できる

構造体とポインタ

  • 関数を呼び出すと、値はパラメータ変数にコピーされる
  • 他の型と同様に、structへのポインタを作成できる
  • pointer->fieldは(*pointer).fieldと同じである

共用体とビットフィールド

  • unionは、同じメモリ位置に異なるデータ型を格納できる
  • 指示付き初期化子は名前でフィールド値を設定する
  • 指示付き初期化子はC99で使える。Objective-Cでは使えるが、C++ではサポートされていない。
  • 中括弧({})で値を指定してunionを宣言すると、その値を最初のフィールドの型で格納する
  • unionのあるフィールドに書き込んだ値を別のフィールドとして読みだす場合、コンパイラは警告を出さない
  • enumはシンボルを格納する
  • ビットフィールドは任意のビット数でフィールドを格納できる
  • ビットフィールドはunsigned intとして宣言すべきである
#include <stdio.h>

typedef enum {
    COUNT, POUNDS, PINTS
} unit_of_measure;

typedef union {
    short count;
    float weight;
    float volume;
} quantity;

typedef struct {
    const char *name;
    const char *country;
    quantity amount;
    unit_of_measure units;
} fruit_order;

void display(fruit_order order)
{
    if (order.units == PINTS)
        printf("%.2fパイントの%sです\n", order.amount.volume, order.name);
    else if (order.units == POUNDS)
        printf("%2.2fポンドの%sです\n", order.amount.weight, order.name);
    else
        printf("%i個の%sです\n", order.amount.count, order.name);
}

int main()
{
    fruit_order apples = {"りんご", "イギリス", .amount.count=144, COUNT};
    fruit_order strawberries = {"いちご", "スペイン", .amount.weight=17.6, POUNDS};
    fruit_order oj = {"オレンジジュース", "アメリカ", .amount.volume=10.5, PINTS};
    display(apples);
    display(strawberries);
    display(oj);

    return 0;
}

Cのデータ型

char:文字コードの数値
int:整数(少なくとも15ビット)
short:整数(intの半分)
long:整数(少なくとも32ビット)
float:浮動小数点数
double:倍精度浮動小数点数

関数宣言

  • コンパイラは未知の関数への呼び出しがあると、その関数がintを返すとみなす
  • そのため、関数を定義する前に呼び出すと、問題となる場合がある
  • 関数宣言は、関数を定義する前に関数の概要をコンパイラに知らせる
  • 関数宣言をソースコードの先頭に記述すると、コンパイラが戻り値の型で混乱することはない
  • 関数宣言は、多くの場合、ヘッダファイルに入れる
  • #include を使って、コンパイラにヘッダファイルの内容を読み込むよう指示できる
  • #include <ファイル名>とすると、コンパイラは標準ヘッダファイルのディレクトリからファイルを探す
  • #include "ファイル名"とすると、コンパイラは現在のディレクトリからファイルを探す

コードの共有

  • コードを共有するには、そのコードを別のCファイルに入れる
  • 関数宣言は別の.hヘッダファイルに入れる
  • 共有コードを使いたいCファイルで、そのヘッダファイルをインクルードする
  • コンパイラコマンドでは、必要となる全てのCファイルを列挙する

例:hide_massage.cで、共有コードencrypt.cを使う場合
(1) encrypt.cの冒頭で #include "encrypt.h"
(2) hide_message.cの冒頭で、 #include "encrypt.h"
(3) コンパイラコマンドで、必要となる全てのCファイルを列挙:gcc hide_message.c encrypt.c -o hide_message

コンパイル時間の短縮(オブジェクトコードのキャッシュとmakeによる自動化)

  • 大量のファイルをコンパイルする場合、長い時間がかかることもある
  • オブジェクトコードを*.oファイルに格納すると、コンパイル時間を短縮できる
  • gccは、ソースファイルだけでなくオブジェクトファイルからもプログラムをコンパイルできる
  • makeツールはビルドの自動化に利用できる
  • makeはファイル間の依存関係を知っているので、変更したファイルだけをコンパイルできる
  • makeには、makefileを使ってビルドについて知らせる必要がある
  • makefileでは、行のインデントには空白ではなくタブを使う

  • コマンドラインで > を使うと、標準出力をファイルにリダイレクトできる
  • 2> を使うと、標準エラーをリダイレクトできる
  • printf()は、fprintf(stdout)のショートカットである
  • fprintf(stderr)で標準エラーを出力できる
  • 通常、1つのプロセスが持てるデータストリームの数は256個まで
  • コマンドライン引数は、文字列ポインタの配列としてmain()に渡される
/**
* ピザの注文を処理するプログラム
*/

#include <stdio.h>
#include <unistd.h> // POSIXライブラリ(getopt()を使うために読み込み)

int main(int argc, char *argv[]) // argcには引数の個数、argvには引数が入っている
{
    char *delivery = "";
    int thick = 0;
    int count = 0;
    char ch;

    // getopt()の第3引数で、オプションの名前を指定する。
    // getopt()の第3引数で、オプション名の後ろに:をつけると、オプション名の後ろに引数が続くことを示す
    while ((ch = getopt(argc, argv, "d:t")) != EOF) { // dオプション(引数有り)とtオプション(引数無し)が有効
        switch (ch) {
            case 'd':
                delivery = optarg; // optargはオプションの引数
                break;
            case 't':
                thick = 1;
                break;
            default:
                fprintf(stderr, "Unknown option: '%s'\n", optarg);
                return 1;
        }
        // optindにはコマンドラインから読み込んだ文字列数が格納される
        argc -= optind;
        argv += optind;

        if (thick)
            puts("Thick crust.");

        if (delivery[0])
            printf("To be delivered %s.\n", delivery);

        puts("Ingredients:");

        for (count = 0; count < argc; count++)
            puts(argv[count]);
    }

    return 0;
}

感想

標準入力・標準出力・標準エラーや、リダイレクト、パイプ、小さなツールの組み合わせ等、CプログラムというよりはUnix(の、主にコマンドライン)入門という趣の章だった。

そもそもCはUnixのために作られた言語なので、内容的には妥当だと思う。ただ、Windows環境の人は違和感を感じる章かも。

また、本章で題材とした「CSVからJSONへの変換」のような仕事を行う「小さなツール」は、Cよりも、スクリプト言語で書いたほうが簡単。例えばRubyなら、CSVからJSONへの変換は、以下の3行で書ける(CSVデータの取り扱いは面倒なので、自前で実装するのではなく、言語やライブラリの力を借りるべき)。

require 'csv'
require 'json'

CSV.parse(data).to_json
出典:http://stackoverflow.com/questions/5357711/csv-to-json-ruby-script

  • string.hヘッダファイルには、便利な文字列関数が含まれている。
  • strstr(a,b)は、文字列aの中の文字列bのアドレスを返す。
  • strcpy()は、ある文字列を別の文字列にコピーする。
  • strcmp()は2つの文字列を比較する。
  • strcat()は2つの文字列を連結する。
  • strchar()は、文字列内の文字の位置を見つける。
  • strlen()は、文字列の長さを求める。
/* 入力文字列を反転して表示するプログラム */

#include <stdio.h>
#include <string.h>

void print_reverse(char *s)
{
    size_t len = strlen(s);
    char *t = s + len - 1;
    while (t >= s) {
        printf("%c", *t);
        t = t - 1;
    }
}

int main()
{
    char str[100];
    printf("反転する文字列を入力して下さい(100文字以内):");
    fgets(str, sizeof(str), stdin);
    print_reverse(str);
    return 0;
}

このページのトップヘ