K3の住民

最近レア社のことをほとんど書いていない自称レア社好きがゲームやプログラミングについて色々書いていく。

JavaScriptで学・・・べないCの共用体

2019/02/01 一番最後が分かりづらかったので分かりやすく訂正

皆さん、こんにちは。雪圀です。
今回はCの共用体をJavaScriptを通して学ぼうと思います・・・が、今回は正直分かり
づらいので、普通にCの共用体でググった方が早く理解出来ると思います。


一応JavaScriptでも似たようなものを作ることが可能でした・・・が、正直Cの共用体
の劣化であるとしか言いようがありません。JavaScriptでCのような共用体を作るのは
不可能と言って良いでしょう。

Cによるデータ型のサイズ

Cにおけるデータ型にはサイズがあり、これと符合の有無(負の値が使えるか使え
ないか)により変数の最大値は決定されます。例えばchar型の場合サイズは1byte
なので、0b1000 0000~0b0111 1111、つまり、-128~127まで扱うことが出来ます。
それぞれのサイズ、最大値と最小値をまとめた表は以下の通りです(とは言っても
文字数の都合上省略しているところもありますが・・・)。

最小値
最大値
データ型
サイズ
符号あり
符号無し
符号あり
符号無し
char
1byte
-128
0
127
255
short
2byte*1
-32768
0
32767
65535
int
4, 8byte*2

文字数の都合上省略
long
4, 8byte*3
long long
8byte
負の最小値
正の最小値
負の最大値
正の最大値
float
4byte
-10^-38
10^-38
-10^38
10^38
double
8byte
-10^-308
10^-308
-10^308
10^308
long double
16byte
-10^-4932
10^-4932
-10^4932
10^4932

ここで少し補足ですが、float、double、long doubleには符号無しがありません。

で、何故データ型のサイズの話を?と思われたかと思いますが、このデータ型の
サイズが共用体を知るうえで重要となります。

JavaScripにおける参照渡し

JavaScriptでは、Cのポインタと同じように、変数の参照先を別の変数に渡すこと
が可能です。
但し、ここからが問題で、Cでは以下のようなことが可能ですが、JavaScriptでは
出来ません(そもそもJavaScriptってキャスト演算子使えないですし)。

short *val;                 // 2byte
char valc[2] = { 54, 32 };  // 1byte + 1byte = 2byte

val = (short *)valc;        // valc[1] = 0b0010 0000  valc[0] = 0b0011 0110
                            //  -> 0b0010 0000 0011 0110 -> val = 8246

どうでもいいんですが、ソースコードを埋め込むはてな記法があったんですね・・・
今まで引用を使ってました。これ言語指定したら色付けしてくれるし楽ですね。

では、早速JavaScriptで参照渡しをやってみましょう。

var val1, val2, msg;

val1 = { x: 0 };                                            // val1.x = 0
val2 = val1;                                                // 参照渡し

val2.x = 255;                                               // val2.xの値を変更

msg = "val1.x: " + val1.x + "\nval2.x: " + val2.x + "\n";   // 結果出力用
console.log(msg);                                           // 結果を出力

この結果は以下のようになります。

val1.x: 255
val2.x: 255

「あれ?val2.xの値を変えただけなのにval1.xの値も変わったぞ?」と思ったこと
でしょう。
実は、これこそが参照渡しの特徴であり、ポインタの参照渡しとほぼ一緒の考え方で
あり、共用体とも若干考え方が一緒です。

共用体とは

では、共用体について解説していきます。ここからはJavaScriptは全く使わないので、
Cが苦手だーって奴は退出してもらっても構いません。
共用体は、構造体と書き方そのものは全く同じです。しかし、構造体と違うのは格納
出来る値が一つのみであるという点です。
格納できる値が一つのみである、とは言っても、複数のデータ型があるならばその
うちどのデータ型で格納してもいいよっていうのが共用体です。
例えば、以下のような宣言をしたとします。

union
{
    float f;        // 4byte
    short s;        // 2byte
    char c;         // 1byte
} u_test;

ではu_test.fに1.024を代入してみましょう。

u_test.f = 1.024;     // 0b0011 1111 1000 0011 0001 0010 0110 1111

そして、u_test.fを実数として、u_test.s、u_test.cを整数として表示させてみます。
すると

u_test.f = 1.024000
u_test.s = 4719
u_test.c = 111

このようになります。何故u_test.sは4719に、u_test.cは111になったのでしょう?
u_test.fはfloat型で、float型は以下のように構成されています。

f:id:ryoryoau24:20190131223854p:plain

先ほどのu_test.fに1.024を代入した辺りのコメントは1.024を2進数で表したものです。
この下位8bitを見てみましょう。この8bitを10進数に直すと、

0b0110 1111  ->  111

u_test.cの値と同じとなりました。次に下位16bitを10進数に直すと、

0b0001 0010 0110 1111  ->  4719

u_test.sの値と同じとなりました。

これで分かったのでは無いでしょうか。共用体は、一つの確保された領域の値を
複数のデータ型で使いまわすことが出来るものなのです。

一応このようなことはポインタを使っても可能ではあります。しかし、ポインタと
違うのは、ポインタ変数の領域を必要としないのでポインタよりもメモリを消費する
ことがありません。あくまで一つの領域を確保するだけなのです。
また、ポインタの場合、参照渡しをする場合サイズを合わせないといけませんが、
共用体の場合はその必要がありません。

共用体は最近ではほとんど使われなくなりました・・・とほとんどのCの解説本に
書かれてるみたいですが、一つだけ言わせていただきます。

そんなことは無いぞ、と

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

ビットフィールドとは、構造体で使うことの出来る、使うbit数を指定出来る優れ
ものです。
例えば、unsigned char型の4bitだけ扱える変数を作りたい場合、

unsigned char b4 : 4;

構造体内でこのようにすることで4bitの変数を作ることが出来ます。
ん、これじゃあ共用体と関係なくね、と思われた方。共用体は1つの領域の値を
複数のデータ型で使いまわせる、と書きました。つまり、このようにすることも
出来ないでしょうか?

union
{
    unsigned char byte;             // byte単位
    
    struct
    {                               // bit単位
        unsigned char b0 : 1;       // 0bit目
        unsigned char b1 : 1;       // 1bit目
        unsigned char b2 : 1;       // 2bit目
        unsigned char b3 : 1;       // 3bit目
        unsigned char b4 : 1;       // 4bit目
        unsigned char b5 : 1;       // 5bit目
        unsigned char b6 : 1;       // 6bit目
        unsigned char b7 : 1;       // 7bit目
    } bit;
} reg;

このように書くと、byte単位で変数を扱うことも出来るし、bit単位で変数を扱うこと
も出来ます。

このテクニックは主に組込みプログラミングで筐体を制御したい場合に使われます。
せっかくなのでちょっとした例を出してみます。

2つのフルブリッジ回路によるモータの制御を行う

こういう想定でやってみます。使う制御用レジスタは出力専用であり、一つだけと
します。メモリアドレスの割り当て等は省略します。
byte単位の制御とbit単位の制御も行いたいですが、フルブリッジ回路は1つにつき
4つ信号端子があるので4bit単位の操作も行いたいですね。
その場合はこのようにします。

union motor_reg
{
    unsigned char BYTE;             // byte単位
    
    struct
    {                               // 4bit単位
        unsigned char M1 : 4;       // 1つ目のモータ
        unsigned char M2 : 4;       // 2つ目のモータ
    } BIT_4;
    
    struct
    {                               // bit単位
        unsigned char B0 : 1;       // 0bit目
        unsigned char B1 : 1;       // 1bit目
        unsigned char B2 : 1;       // 2bit目
        unsigned char B3 : 1;       // 3bit目
        unsigned char B4 : 1;       // 4bit目
        unsigned char B5 : 1;       // 5bit目
        unsigned char B6 : 1;       // 6bit目
        unsigned char B7 : 1;       // 7bit目
    } BIT;
} MOTOR;

// 以降メモリアドレスの割り当て

次にモータ1つにつきどのように操作したらどのように動くかを示します。

f:id:ryoryoau24:20190201010101p:plain

4~7bit目は0~3bit目と同じ値とします。
では両方とも正転させてみます。その場合、このようにすることでモータ2つを正転
させることが出来ます(3つありますが、そのうちどれか1つだけ書けば良いです)。

// byte単位での制御
MOTOR.BYTE = 0x99;
// 4bit単位での制御
MOTOR.BIT_4.M1 = 0x9;
MOTOR.BIT_4.M2 = 0x9;
// bit単位での制御
MOTOR.BIT.B0 = 1;
MOTOR.BIT.B1 = 0;
MOTOR.BIT.B2 = 0;
MOTOR.BIT.B3 = 1;
MOTOR.BIT.B4 = 1;
MOTOR.BIT.B5 = 0;
MOTOR.BIT.B6 = 0;
MOTOR.BIT.B7 = 1;

メモリアドレス割り当て等もあるのでこれだけで全てが出来るわけではありませんが
基本的な組込みプログラミングはこんな感じです。


以上です。JavaScriptでCの共用体は・・・学べませんでしたね。というか構想では
ちょっとしたテクニックを紹介して終わりだったはずなのにどうしてこうなった。

今回はこれくらいにしときます。

*1:SILP64のみ8byte

*2:環境により異なる。IP32やそれより古いOSでは2byte

*3:環境により異なる