IoT 用デバイスや、リアルタイム性が要求されるような機器を開発する時、ワンチップマイコンが使われることが依然としてあるかと思う。... 続きを読む
「C言語」カテゴリーアーカイブ
C言語とポインタ
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;
}
一般的には、こういう説明だろう。
図示すると次のようになるだろうか。

#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;
}
どのようにコンパイルされるか見たことがないので確信が無いが、
構造体自身をリターンできるのであれば、前者の方法でも良いかもしれない。... 続きを読む C言語とポインタ
加筆修正したバージョン... 続きを読む
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
C言語を学ぶべきかどうか
ahrefsでC言語に関する検索上位のページを調べてみると、C言語をおすすめしない理由などが挙がっている。
初学者が、ということかもしれないが、そんな記事が google の上位に来てアクセスが集まるのはとても悲しいので、C言語について少し書いてみようと思う。
典型的には、言語的なものについて考えるならば、
- 組み込み系
- アセンブラ
- C言語
- WEBサイト
- HTML/CSS/Javascript
- PHP or Java or Ruby
- DB(SQL)
- スマートフォンアプリ
- HTML/CSS/Javascript
- ネイティブアプリの場合は、Swift(ios)/Java or Kotlin(android)
- データサイエンス
- Python
- デスクトップアプリ
- .net