バイナリファイルから float 型データを読み込む

ちょっと調子に乗ってたらあんまりにもひどい勘違いに数時間潰したので自戒を込めて晒してみる。

問題と答え(C言語

バイナリファイルからfloat型のデータをひとつ読み込むには?
答え:

fread(&float, sizeof(float), (size_t)1, fp);

如何に遠回りをしたか

最初、とあるバイナリファイルからfloat型のデータを読み込もうとして、次の文字を見つけた

00 00 80 3f

バイナリエディタ等で見てみるとfloat型で1.0になっている。
え、これで1.0なの?
と思っていると、これは単精度浮動小数点数をリトルエンディアンで表記しているからこうなる。
だから読み込むにはビッグエンディアンに戻さないといけない(第一の間違い)

	unsinged char c;
	FILE *fp;
	char str[256];
	//(途中略)
	fp = fopen("./hoge.bin", "rb");
	c=fgetc(fp);
	str[3]=c;
	c=fgetc(fp);
	str[2]=c;
	c=fgetc(fp);
	str[1]=c;
	c=fgetc(fp);
	str[0]=c;
	str[4]='\0';
	// リトルエンディアン文字列をとりあえずビッグエンディアンで詰め直す

	sprintf(str, "%2.2x%2.2x%2.2x%2.2x", str[0],str[1],str[2],str[3]);
	// 16進数 2桁ずつ表記の文字列にして詰め直し

トルエンディアンの文字列をビッグエンディアンに戻すことはできた。

3f 80 00 00

今度はこれをfloat型に変換する。
文字列から変換指定子を使って変数に読み込ませればいい(第二の間違い)
しかし、16進数表記float型文字列をfloat型変数に変換して代入する方法が見つからない。

	// str = 3f800000
	sscanf(str, "%x", &int);
	// 16進数をint型として解釈する

10進数のint型にする時はこれでいい。
じゃあ同じように16進数float型として解釈すればいいじゃん。
と、思いきや。

	// str = 3f800000
	sscanf(str, "%f", &float);
	// 16進数をfloat型として解釈したい

これだと最初の一文字 3.0 しか代入されない。

	// str = 3f800000
	sscanf(str, "%a", &float);
	// 16進数をfloat型として解釈したい

これでもおんなじ結果。
ここで詰まった。
数時間詰まった。
(実はなぜか一回だけうまくいって、でも再現できなくて、再現したくてこだわり続けてしまった。たぶん何かの間違いだった)

たったひとつの冴えたやり方

バイナリファイルのリトルエンディアンをビッグエンディアンに戻して、16進数float型を10進数float型に戻す、そんな処理の関数を書くか?
っていうか誰も作っていないのか?
ググってもなんで見つからないんだ?
標準で用意されてるのか?
それどこヘッダー? どこヘッダーよそれー?
と、書くか探すか迷いながらググっていると、ふとこんな文を見つけた。

バイナリで書かれているデータをどうして変換する必要があるのでしょう?
変換の必要が無いからバイナリなのでは?

http://rararahp.cool.ne.jp/cgi-bin/lng/vc/vclng.cgi?print+200208/02080103.txt

確かに仰る通りごもっとも。

少し検索ワードを変えてググってみた。
そして見つけた。
答え:

fread(&float, sizeof(float), (size_t)1, fp);

結論

すぐに思いつくシンプルで素晴らしい解法は、たいてい既に誰かが用意している。

余談:開発環境

OS: Snow leopard 10.6.8
gcc version: i686-apple-darwin10-gcc-4.2.1
インテルの CPU はリトルエンディアンで処理するので、 Windows のバイナリファイルを読み込む時でも気にする必要ないみたい。
Mac OS X のような PowerPC 系のシステムと互換性のある仕様にしたかったら、こう簡単にはいかないのかもしれない(よく分からない)。
面倒だから切り捨てたくなるね!

オブジェクト指向がよく分かんない

C言語の勉強をしばらくやってた。
するとだんだん、プログラミングとは、手続きをひとつひとつ書き下していくことのように思えてくる。

そこそこC言語を勉強した後、いま流行っているらしいオブジェクト指向プログラミングの勉強として Java に手を出した。
そしたらオブジェクト指向が全然分からん。
クラスが存在するのは分かる。継承の概念も何となく分かる。でも interface とか必要性が全く分からん。なんのために存在するのよ。
どうもオブジェクト指向は、今までC言語でやっていたような、手続きをずっと積み重ねていくプログラミングとは色々違うらしい。
よく分かんないから、とりあえず後回しにした。

ふと、 Python を使ってみると、文字列の処理がビックリするほど便利だった。
ちょっとオブジェクト指向に興味が湧いた。
オブジェクト指向的な話題は少しずつ触れるようになった。

しばらくたって、オブジェクト指向の「データと手続きをひとまとめにする」という考え方には納得した。
データを、扱う手続き以外からはなるべく隠すということ。
関数やファイルごとのスコープの違いと実用性についてなんとなく分かってきた頃だった。
構造体って結構便利。構造体の型名で変数名を定義するのってなんかクラスっぽい。

最近、C言語のポインタの本を読んだ。
ふとその背表紙を見かけた時、友人が昔読んでいたのを思い出した。
実際、ポインタに自信はなかったし。
そこに、いつ使うのか全く分からなかった共用体の使い方とポリモルフィズムについて書かれていて、いろいろと腑に落ちた。
オブジェクト指向なんとなく便利そう、と思えた。

文字列クラスぽいものを作ってみようと思った。
文字列を保持し、文字数、最初の文字、最後の文字を返す、という三つの手続きを持つ。
もしC言語で作るとしたらこんな感じだろうか。

mystring.h 文字列クラス String を定義するヘッダ

/* mystring.h */
#ifdef __cplusplus
extern "C" {
#endif

#ifndef _mystring_
#define _mystring_

// ここに書いてる関数内で使ってるライブラリ
#include <string.h>
#include <stdlib.h>

// String をまず不完全型として定義
typedef struct String_tag String;

// String を実際に定義
struct String_tag {
	char *str;
	size_t (*Length)(String *obj);		// 文字列の長さを返す
	char (* LString)(String *obj);		// 文字列の最初の文字を返す
	char (* FString)(String *obj);		// 文字列の最後の文字を返す
};
// typedef したあとに定義するこの流れ、まだよく分かってない
// そもそも正しくない?

// 文字列を入れると領域を確保
String *stringInit(const char *s);

// String オブジェクトとして確保した文字列のメモリ解放
// あと手続きを使用不可にする
void stringFree(String *obj);

#endif

#ifdef __cplusplus
}
#endif

string.c 文字列クラス String の手続きを定義するソースファイル

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "mystring.h"

// 関数プロトタイプ宣言いくつか
static size_t st_length(String *obj);
static char st_fString(String *obj);
static char st_lString(String *obj);
static void *st_finish(String *obj);

// 文字列を入れると領域を確保
String *stringInit(const char *s)
{
	// 確保するオブジェクト
	String *obj;

	// 渡された文字列のチェック必要?
	if( 1>=strlen(s) )return NULL;

	// 構造体分のメモリ確保
	obj = malloc( sizeof(String) );
	if( NULL == obj )exit(EXIT_FAILURE);
	// エラーチェック面倒だけど一応やっとく
	
	// 関数ポインタの初期化
	obj->Length = st_length;
	obj->LString = st_lString;
	obj->FString = st_fString;
	
	// 文字列分のメモリ確保
	obj->str = malloc( sizeof(char)*(strlen(s)+1) );
	if( NULL == obj->str )exit(EXIT_FAILURE);
	// エラーチェック面倒だけど一応やっとく

	// やっと文字列代入
	strncpy(obj->str, s, strlen(s)+1);

	// 確保成功
	return obj;
}

// 文字列の長さを返す( '\0' は含まない)
static size_t st_length(String *obj)
{
	return 	strlen(obj->str);
}

// 文字列の最初の文字を返す
static char st_fString(String *obj)
{
	return obj->str[0];
}

// 文字列の最後の文字を返す
static char st_lString(String *obj)
{
	return obj->str[strlen(obj->str)-1];
}

// 何を渡されても NULL を返す
static void *st_finish(String *obj)
{
	return NULL;
}

// String オブジェクトとして確保した文字列のメモリ解放
// あと手続きを使用不可にする
void stringFree(String *obj)
{
	free(obj->str);
	obj->str = NULL;

	// 関数ポインタを NULL で埋めず、 NULL を返す関数で上書き
	// この代入は戻り値が違うからコンパイル警告が出てしまう・・・
	obj->Length = st_finish;
	obj->LString = st_finish;
	obj->FString = st_finish;

/*
	// NULL で埋めた関数ポインタにアクセスした時、
	// セグメンテーションフォルトで落ちてしまう。
	// エラーを返すようにしたいので避けた。
	obj->Length = NULL;
	obj->LString = NULL;
	obj->FString = NULL;
*/
}

main.c 実際に String クラスっぽいものを使ってみるソースファイル

#include <stdio.h>
#include <stdlib.h>
#include "mystring.h"

int main(int argc, char *argv[])
{
	String *hello =stringInit("Hello, world!");
	// オブジェクトを初期化
	
	// 初期化エラーの確認いる?
	if( NULL == hello )exit(EXIT_FAILURE);

	// 文字列にアクセスと表示
	printf("%s\n", hello->str);

	printf("String Length: %d\n", (int)hello->Length(hello));
	printf("First character: %c\n", hello->LString(hello));
	printf("Last character: %c\n", hello->FString(hello));
	// オブジェクトの手続き呼び出しにいちいち自分自身を引数にするのって面倒くさい

/*
	// オブジェクトの関数ポインタごしだと使えるけど、
	// 直接使おうとするとコンパイルエラー。ええ感じ。
	st_length(hello);
	st_fString(hello);
	st_lString(hello);
*/

	// オブジェクトを解放
	stringFree(hello);
	printf("\nString Free.\n");

/*
	hello=NULL;
	// こう NULL で埋めてしまうと、これ以降に hello の手続きにアクセスした時に
	// セグメンテーションフォルトで落ちてしまう。
	// 落ちずにエラーを返させるため NULL で埋めずに生かしとくけど、
	// もっと良い方法ないかな・・・。
*/	
	// stringFree 後に手続きを呼び出しても安全(?)にエラーを返すことを確認
	printf("String Length: %d\n", (int)hello->Length(hello));
	printf("First character: %c\n", hello->LString(hello));
	printf("Last character: %c\n", hello->FString(hello));

	// 終了。お疲れさまでした。
	return 0;
}

実行結果。

imadedede$ ./string_c
Hello, world!
String Length: 13
First character: !
Last character: H

String Free.
String Length: 0
First character:
Last character:

結論:C言語じゃクラスを書くの面倒。
なので、今はとりあえず C++ の本を読んでる。
いろいろソフトウェアを作ってみたいな。

余談。
この記事書く時はポリフォリズムと憶えていた。でも検索しても Wikipedia がトップに出てこないなんておかしいなーと思っていたら、正解はポリモーフィズム(ポリモルフィズム)みたいだった。
カタカナ語難しい。