2012-03-24 28 views
5

バイナリファイルからデータを読み込み、構造体に格納しようとしています。ファイルを構造体に読み込む(C++)

03 56 04 FF FF FF ... 

そして、私の実装は次のとおりです:data.binの 最初の数バイトがある

#include <iostream> 
#include <fstream> 

int main() 
{ 
    struct header { 
     unsigned char type; 
     unsigned short size; 
    } fileHeader; 

    std::ifstream file ("data.bin", std::ios::binary); 
    file.read ((char*) &fileHeader, sizeof header); 

    std::cout << "type: " << (int)fileHeader.type; 
    std::cout << ", size: " << fileHeader.size << std::endl; 

} 

私は期待していた出力がtype: 3, size: 1110ですが、いくつかの理由で、それはtype: 3, size: 65284だ、そうでは、基本的には、第2のバイトファイルはスキップされます。ここで何が起こっていますか?

+1

'はsizeof(ヘッダ)が'何ですか?私はそれが4であることを賭けて喜んで... – Cameron

+0

ええ、ええ。私はそれをチェックすべきだった。 – vind

答えて

7

実際、動作は実装定義です。実際にあなたのケースで実際に起こっているのはおそらく、typeの構造体のメンバーの後に1バイトのパディングがあり、その後に2番目のメンバーsizeが続きます。私は出力を見た後、この議論に基づいた。ここで

は、あなたの入力バイトです:

03 56 04 FF FF FF 

最初のバイト03typeある構造体の最初のバイトに行き、あなたが出力としてこの3を参照してください。次に、次のバイト56は、パディングである第2のバイトに移動し、次の2バイトの04 FFは、size(サイズは2バイト)の次の2バイトに移動します。リトルエンディアンのマシンでは、04 FF0xFF04と解釈されますが、出力として得られるのは66284です。

基本的にコンパクトな構造体が必要なので、パディングを詰めます。 #pragmaパックを使用してください。しかし、そのような構造体は通常の構造体に比べて遅いでしょう。最後の行を書くための別の方法はこれです

char bytes[3]; 
std::ifstream file ("data.bin", std::ios::binary); 
file.read (bytes, sizeof bytes); //read first 3 bytes 

//then manually fill the header 
fileHeader.type = bytes[0]; 
fileHeader.size = ((unsigned short) bytes[2] << 8) | bytes[1]; 

fileHeader.size = *reinterpret_cast<unsigned short*>(bytes+1); 

しかし、それはエンディアンに依存しますので、これは、実装定義であるより良いオプションは、として、手動で構造体を埋めるためにあります機械のリトルエンディアンマシンでは、ほとんどの場合動作します。

フレンドリーなアプローチは、この(実装定義)のようになります。

std::ifstream file ("data.bin", std::ios::binary); 
file.read (&fileHeader.type, sizeof fileHeader.type); 
file.read (reinterpret_cast<char*>(&fileHeader.size), sizeof fileHeader.size); 

しかし、再び、最後の行は、マシンのエンディアンに依存します。

+0

パディングはファイルではなく構造体になります。プラグマパックを試してください:http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=VS.100%29.aspx –

+1

ありがとう!わかった。 '#pragma pack(1)'がその仕事をしました。 – vind

+1

この非常に詳細な説明をお寄せいただきありがとうございます。 推奨するように、構造体の各メンバーを単独で埋めます。それは一番うまくいくようです。 – vind

1

まあ、それは構造体のパディングかもしれません。現代のアーキテクチャで構造体を高速に動作させるために、いくつかのコンパイラはそこにパディングを置いて、4または8バイトの境界に整列させたままにします。

これをプラグマまたはコンパイラ設定で上書きできます。例えば。ビジュアルスタジオ/Zp

もしこれが起きていたら、最初のcharに値56があり、次のnバイトがpaddingに読み込まれ、次に2がshortに読み込まれます。 2番目のバイトがパディングとして失われた場合、次の2バイトがshortに読み込まれます。そして、ショートに現在データ'04 FF 'が含まれているので、これは(リトルエンディアンで)0xff04に相当し、これは65284です。

0

コンパイラは、2または4の倍数のバイト数にパッド構造体を埋め込み、マシンコードに簡単にアクセスできるようにします。本当に必要な場合を除き、私は#pragma packを使用しません。これは通常、(ファームウェアレベルのような)本当に低いレベルで作業している場合にのみ適用されます。マイクロプロセッサは、4または2の倍数であるアドレスにメモリにアクセスするための具体的なoperantionsを持っている、そしてそれは作るためにソースコードを簡単になりますので、たまたま Wikipedia article on that.

は、それがより効率的にメモリを使用し、時にはコードが少しはありますもっと早く。プラグマ・パック・ディレクティブのように、その動作を止める方法はありますが、コンパイルに依存します。しかし、コンパイラのデフォルトをオーバーライドすることは、通常、悪い考えです。コンパイラの人は、そのように動作させる非常に良い理由がありました。

私にとってより良い解決策は、非常に単純であり、良いプログラミングの慣例に従う純粋なCで解決することです。コンパイラがデータを低くして何をしているのか決して頼ることはありませんレベル。

#pragma pack(1)を実行するだけで、セクシーでシンプルなことが分かり、コンピュータの邪魔になっていることを直接処理して理解していることがわかりました。最高のソリューションは、常にあなたが使用している言語で実装されているものです。理解しやすく、保守が簡単です。それはどこでも動作するはずですので、それは、デフォルトの動作だ、と、この特定のケースでは、C液は本当に簡単かつstraightfowardです:ちょうど属性によって、あなたの構造体の属性を読んで、次のように:

void readStruct(header &h, std::ifstream file) 
{ 
    file.read((char*) &h.type, sizeof(char)); 
    file.read((char *) &h.size, sizeof(short)); 
} 

(これは動作しますあなたの場合構造体をグローバルに定義する)

さらに、C++で作業しているので、その読み込みを行うメンバメソッドを定義し、後でmyObject.readData(file)を呼び出すだけです。美しさとシンプルさが見えますか?

読みやすく、保守しやすく、コンパイルしやすく、より高速で最適化されたコードになります。デフォルトです。

私が何をしているかについてはっきりしていない限り、通常は#pragmaディレクティブを混乱させたくありません。その意味は驚くべきものです。

+0

ビッグエンディアンマシンで動作しますか? – Nawaz

+0

ファイルを生成したプログラムがビッグエンディアンのマシンで実行された場合、はい。ビッグエンディアンマシン上のリトルエンディアンファイルで作業するコードを記述する必要がある場合は、それを明示的に処理する必要があります。 – Castilho

+0

これは、あなたの主張に反してどこでも動作しないことを意味しています*「どこでもうまくいくはずです」*、そして確かに最良のアプローチではありません。私はいくつかのアプローチを提供しており、それぞれの問題があればそれについても言及しています。 – Nawaz

0

あなたはパディングの問題を上書きする#pragma packコンパイラ・ディレクティブを使用することができます。

#pragma pack(push) 
#pragma pack(1) 
struct header { 
    unsigned char type; 
    unsigned short size; 
} fileHeader; 
#pragma pack(pop)