「C言語」カテゴリーアーカイブ

共用体

さて、構造体の次に出てくるのが共用体だろう。 これが、今思うと、自分には、超難解だった。ネット上の説明も、まあまあ、デタラメだと思う。 私がこれまで携わってきた中では、以下の二つのケースが大半だったので、私見だが、使う必要が出てきた時に考えればよいと思う。

可変サイズのデータの格納

typedef union {
    char charval;
    int intval;
    long longval;
    double doubleval;
} variant_t;


int main(int argc, char *argv[])
{
    variant_t v;

    v.charval = 1;
    v.intval = 2;
    v.longval = 3;
}
一般的には、こういう説明だろう。 図示すると次のようになるだろうか。 union しかし、ネット上の説明がデタラメだと思うのは、いったい、どの共用体メンバーにアクセスしてよいかわからない点だ。 実際には、構造体と組み合わせて使うのではないかと思う。
#include <stdio.h>

// char でも int でも long でも double でも入るデータ構造を考えてみる
typedef struct {
     int valtype; // 1=charval, 2=intval, 3=longval、 4=doubleval
     union {
         char charval;
         int intval;
         long longval;
         double doubleval;
    } u;
} variant_t;

int
main(int argc, char *argv[])
{
    variant_t v;

    v.valtype = 3;
    v.u.longval = 1234567890;

    switch (v.valtype) {
    case 3:
        printf("%ld\n", v.u.longval);
        break;
    default:
        printf("not implemented yet\n");
        break;
    }
    return 0;
}
おそらく、こういった、データの中身に何が入ってもよいような構造を作りたい場合に union を使うことがあるかもしれない。 もちろん、ベタに、
typedef struct {
     int valtype; // 1=charval, 2=intval, 3=longval、 4=doubleval
     struct {
         char charval;
         int intval;
         long longval;
         double doubleval;
     } s;
} variant_t;
とか、
typedef struct {
     int valtype; // 1=charval, 2=intval, 3=longval、 4=doubleval
     char charval;
     int intval;
     long longval;
     double doubleval;
} variant_t;
でもよいのだが、不要なメモリを確保してしまうことになるので、通信など、サイズに制約があるようなデータは、union のようなデータ構造になっているだろう。

ビットフィールド

組み込み用のマイコンでは、ある領域を、バイト単位でもビット単位でもアクセスしたい場合があり、そういった場合に union を使うことも多い。
#include <stdio.h>
#include <stdint.h>

typedef union {
    struct {
        unsigned int b7: 1;
        unsigned int b6: 1;
        unsigned int b5: 1;
        unsigned int b4: 1;
        unsigned int b3: 1;
        unsigned int b2: 1;
        unsigned int b1: 1;
        unsigned int b0: 1;
     } bit;
     uint8_t byte;
} reg_t;

int
main(int argc, char *argv[])
{
    reg_t reg;

    reg.byte = 0xaa;

    // ベタでごめん
    printf("%d ", reg.bit.b7);
    printf("%d ", reg.bit.b6);
    printf("%d ", reg.bit.b5);
    printf("%d ", reg.bit.b4);
    printf("%d ", reg.bit.b3);
    printf("%d ", reg.bit.b2);
    printf("%d ", reg.bit.b1);
    printf("%d\n", reg.bit.b0);

    return 0;
}

... 続きを読む

構造体

未だに構造体に関する検索が多いようなので、少し書いてみようと思う。 他のサイトにも解説があるので、初学者の頃、自分がわかりづらかった点について書いてみようと思う。

構造体とは

複数の変数をひとまとめにして扱うようにしたもの。 例えば、座標のように (x,y) をまとめて扱えると便利だろう。
struct 構造体タグ名 {
     型 変数; // メンバ変数と言う
      :
};
のように宣言する。 例1:構造体の場合
// 実際に変数が割り当てられるわけではない。
struct point_st {
    int x;
    int y;
};

int
main(int argc, char *argv[])
{
    struct point_st point;

    point.x = 10; // 構造体のメンバ変数にアクセスする時は、"." を使う。
    point.y = 20;

    return 0;
}
例2:構造体へのポインタの場合
#include <stdlib.h>
struct point_st {
    int x;
    int y;
};

int
main(int argc, char *argv[])
{
    struct point_st *pointp = (struct point_st *)malloc(sizeof(struct point_st));

    // 構造体へのポインタの場合、->(アロー演算子と呼ばれているようだ)でアクセスする。
    pointp->x = 10;
    pointp->y = 20;

    return 0;
}

わかりづらかった点

色々な書式があってわかりづらい。

以下は同じものだ。 パターン1:
struct point_st {
    int x;
    int y;

    return 0;
};

struct point_st point;
パターン2:
typedef struct point_st {
    int x;
    int y;
} point_t;

point_t point;
パターン3 (タグ名省略):
typedef struct {
    int x;
    int y;
} point_t;
struct 〜 と書くのは、タイプ量も多いし、長くて読みづらいので、私は、パターン3をよく使う。

構造体自身へのポインタを含む場合の書き方がわからない。

リスト構造を作る場合に、構造体自身へのポインタを示す場合の書き方。 パターン1:
struct point_st {
    int x;
    int y;
    struct point_st *next;
};

int main(int argc, char *argv[])
{
    struct point_st *point1p;
    struct point_st *point2p;

    point1p = (struct point_st*)malloc(sizeof(struct point_st));
    memset(point1p, 0, sizeof(struct point_st));
    point1p->x = 1;
    point1p->y = 2;
    point1p->next = NULL;
    point2p = (struct point_st*)malloc(sizeof(struct point_st));
    memset(point2p, 0, sizeof(struct point_st));
    point2p->x = 3;
    point2p->y = 4;
    point2p->next = point1p;
    return 0;
}
パターン2:
typedef struct point_st {
    int x;
    int y;
    struct point_st *next;
} point_t;

int main(int argc, char *argv[])
{
    point_t *point1p;
    point_t *point2p;

    point1p = (struct point_st*)malloc(sizeof(struct point_st));
    memset(point1p, 0, sizeof(struct point_st));
    point1p->x = 1;
    point1p->y = 2;
    point1p->next = NULL;
    point2p = (struct point_st*)malloc(sizeof(struct point_st));
    memset(point2p, 0, sizeof(struct point_st));
    point2p->x = 3;
    point2p->y = 4;
    point2p->next = point1p;
    return 0;
}
同様に、読みやすさから、パターン2を使う。

構造体をどのようにして関数に渡すのかわからない

この構造体を他の関数にどのように渡すべきなのか。
#include <stdio.h>

typedef struct {
    double x;
    double y;
} point_t;

void
print_point(point_t point)
{
    printf("%f,%f\n", point.x, point.y);
}

int
main(int argc, char *argv[])
{
    point_t point;

    point.x = 123.4;
    point.y = 456.7;
    print_point(point);
    return 0;
}
としたいかもしれない。(まあ、これでも動く。) しかし、C 言語の場合は、引数はコピーされて渡される。(値渡し) 構造体は複数の変数を束ねておりサイズが大きく、 コピー時間は、小さなプログラムであれば問題ないが、処理の量が多いと、かなりの時間になる。 そのため、通常はポインタ(=構造体の先頭アドレス)を渡すことが多いと思う。
#include <stdio.h>

typedef struct {
    double x;
    double y;
} point_t;

void
print_point(point_t *pointp)
{
    printf("%f,%f\n", pointp->x, pointp->y);
}

int
main(int argc, char *argv[])
{
    point_t point;

    point.x = 123.4;
    point.y = 456.7;
    print_point(&point);
    return 0;
}

リターン値を構造体にしたい場合はどうするのか

#include <stdio.h>

typedef struct {
    double x;
    double y;
} point_t;

void
print_point(point_t *pointp)
{
    printf("%f,%f\n", pointp->x, pointp->y);
}

point_t
xy_point(double x, double y)
{
    point_t point;
    point.x = x;
    point.y = y;
    return point;
}

int
main(int argc, char *argv[])
{
    point_t point;

    point = xy_point(123.456, 789.123);
    print_point(&point);
    return 0;
}
としたいかもしれない。昔はコンパイルエラーだった。(と思う) 構造体をリターンすることができなかった。 今はコピーされてリターンするようだ。 昔ながらのやりかただと以下のようになる。
#include <stdio.h>

typedef struct {
    double x;
    double y;
} point_t;

void
print_point(point_t *pointp)
{
    printf("%f,%f\n", pointp->x, pointp->y);
}

void
xy_point(double x, double y, point_t *ret)
{
    point_t point;
    ret->x = x;
    ret->y = y;
}

int
main(int argc, char *argv[])
{
    point_t point;

    // point にリターン値を入れてもらうため、構造体へのポインタを渡す
    xy_point(123.456, 789.123, &point);
    print_point(&point);
    return 0;
}
どのようにコンパイルされるか見たことがないので確信が無いが、 構造体自身をリターンできるのであれば、前者の方法でも良いかもしれない。... 続きを読む

malloc の使い方(malloc/free/memset)

この難しいテーマを自分はうまく説明できるだろうか。

C 言語を使うには

以下のいずれかの環境があれば試すことができる。
  • mac に xcode をインストールしている。
  • windows に cygwin(およびgcc) をインストールしている。
  • Linux か FreeBSD をインストールして、Cコンパイラをインストールしている。

malloc とは

malloc は標準で用意されているライブラリだ。 メモリの確保(解放)を行う。 コマンドラインから man malloc と打つと使い方が表示される。Mac の場合次のような感じで表示される。
NAME
calloc, free, malloc, realloc, reallocf, valloc -- memory allocation

使い方

ポインタ = malloc(バイト数);
とコールすると、バイト数分のメモリが確保され、確保したメモリへのポインタが返る。 確保したメモリは、
free(ポインタ);
をコールするまで解放されない。 確保したメモリは初期化されないので、通常は、
memset(ポインタ, 0, バイト数);
のようにして 0 で初期化する。(string.h の include が必要) 文字列の終端が \0(=0) だったり、NULL ポインタ(=0) が データの末尾を表していることが多いため、0 で初期化することが 多いだろう。

指定した数のデータ

実行時に毎回データ数が変わるような場合、malloc でデータ数分のメモリを確保することになるだろう。 int の場合
int n = 100;

int *intp = (int*)malloc(sizeof(int) * n); // int 100個分のデータ領域
memset(intp, 0, sizeof(int) * n);

*(intp + 10) = 123;
intp[10] = 123; // 上と同じ。こちらのほうがわかりやすいと思う。

// コンパイルエラーにはならないが、C 言語では何もチェックしないので
// 異常な動作になる。
*(intp + 1000) = 123;
intp[1000] = 123;
char の場合
int n = 100;

char *strp = (char*)malloc(n); // 100文字分のデータ領域
memset(strp, 0, n);

// example 1
strcpy(strp, "hogehoge");

// example 2
*(strp + 30) = 'a';
strp[30] = 'a'; // 上と同じ

// C 言語では確保したメモリを越えようがコピーするので、
// 異常な動作になる。
strcpy(strp, 100文字を超える文字列へのポインタ);

いわゆるリスト

C 言語でリスト(追加削除のできるデータ構造)を扱おうとするとmalloc を使ってリスト構造を作る必要があるだろう。
typedef struct node_st {
int v;
struct node_st *next;
} node_t;
と宣言されているとして、node_t を一つ分確保する場合。
node_t *nodep = (node_t*)malloc(sizeof(node_t));
memset(nodep, 0, sizeof(node_t));
nodep->v = 123;
nodep->next = NULL; // いちおう初期化

prev_nodep = 前の要素を検索するルーチン();
prev_nodep->next = nodep;
nodep->next = NULL;

ちなみにリストを扱う場合は、man queue にあるようなマクロを使うことができる。 以下のようなリストがある。
  • 単方向リスト SLIST
  • 単方向TAILIQ STAILQ
  • 双方向リスト LIST
  • 双方向TAILQ TAILQ
TAILQ は末尾の要素へのアクセスが速い。 単方向は先頭から末尾へのアクセスのみで、双方向はあるノードの前後のノードへのアクセスができる。 という違いがある。(双方向のほうがデータサイズは大きくなる)... 続きを読む

C言語を学ぶべきかどうか

ahrefsでC言語に関する検索上位のページを調べてみると、C言語をおすすめしない理由などが挙がっている。 初学者が、ということかもしれないが、そんな記事が google の上位に来てアクセスが集まるのはとても悲しいので、C言語について少し書いてみようと思う。 典型的には、言語的なものについて考えるならば、
  1. 組み込み系
    • アセンブラ
    • C言語
  2. WEBサイト
    • HTML/CSS/Javascript
    • PHP or Java or Ruby
    • DB(SQL)
  3. スマートフォンアプリ
    • HTML/CSS/Javascript
    • ネイティブアプリの場合は、Swift(ios)/Java or Kotlin(android)
  4. データサイエンス
    • Python
  5. デスクトップアプリ
    • .net
といった感じだろうか。 実際には、これに加えて、設計、テストが必要になる。これに加えて、ネットワークやサーバOSに関する知識があれば、運用までカバーできるのではないかと思う。 自分でシステムを考えるというのでなければ、狙っている業界に合ったものが必要だろうと思う。おそらく、ロボット・車載・ドローンといった機械の制御、OS、WEBサーバ(apacheなど)、DBサーバなど、速度が求められるものはC言語かそれに近いものだろうし、WEBアプリ、スマートフォンアプリなどのように機能に柔軟性が求められるものはC言語には苦手かもしれない。 自分の興味のある分野に応じて言語を選択すべきだと思うが、C言語はビジネスにならないから選ぶべきではないという意見には、C言語の案件はまだ相当数あるし、単価もそう悪くないよと言いたい。 初学者が学ぶべきかどうか・・・車やロボットを制御したいと思うのであれば最初から学ぶべきだと思う。単にソフトウェアで何ができるのかを学びたいだけならば、python か node.js(javascript) をおすすめする。 C言語は難しいだろうか・・・・自分も多少つまづいた点はあったが、それは、別の記事にでも書いてみたいと思う。... 続きを読む