プロが教えるわが家の防犯対策術!

お世話様です。

unsigned long aaa(32bit)にunsigned char bbb[4](8bit*4)を
コピーする場合に、(1)と(2)で結果が違うのはなぜでしょうか?
よろしくご教授お願いいたします。

(1)memcpy(&aaa bbb, 4);
(2)
{
aaa = 0;
unsigned long ccc = 0;
ccc = static_cast<unsigned long>bbb[0];
aaa |= ((ccc << 24) & 0xFF000000);
ccc = static_cast<unsigned long>bbb[1];
aaa |= ((ccc << 16) & 0x00FF0000);
ccc = static_cast<unsigned long>bbb[2];
aaa |= ((ccc << 8) & 0x0000FF00);
ccc = static_cast<unsigned long>bbb[3];
aaa |= ((ccc << 0) & 0x000000FF);
}

【環境】CPU:Pentimum4 OS:WindowsXP コンパイラ:VC++8.0

A 回答 (4件)

この問題は、本質的には、プロセッサが扱う基本データと、物理的なメモリのデータサイズが異なることから発生しています。



つまり、プロセッサが、通常、32bit の int としてして扱うときには、プロセッサ内部では、32bit のレジスタに保持されます。ところが、これをメモリとやりとりする際、メモリの方は、8bit 区切りとしてデータを扱っています。このために、32bit レジスタ1個に入っているデータをメモリに保存する(または、この逆)の場合、メモリ中の4個の領域を使う必要があります。このときに、「メモリの中の4個の領域」をどのような順番で使うかが、エンディアンの問題です。
つまり、プロセッサがデータをメモリにどう配置するかということです。

※もっと言えば、int は、メモリの複数領域に存在するわけですが、int という変数の「アドレス」として、どの番地 ― 最上位桁が入っているところなのか、最下位桁が入っているところなのか ― の違いです。

一方で、CやC++ の配列は、先頭の要素から、順番にメモリ上に配置されることになっています。
※この場合、配列のアドレスとしては、「先頭の要素が存在するメモリのドレス」になります。これは、エンディアンと関係なく同じです。

これに対して、memcopy() は、文字通り、メモリの配置を換えずにそのままコピーします。ですから、数値データの配置順序と、配列の配置順序が一致しているか異なっているかで、結果が違ったり同じだったりします。
配置のタイミングとしては以下の見解であってますでしょうか?

ですから、
(1)アドレスを伴った間接代入時には、配置は行われない(memcpy)
これは、アドレスを伴っているからではなく、memcpy() という、配置を換えずにコピーする関数を使っているから、配置がそのままでコピーされるわけです。

(2)直接代入時に配置換えが行われる。(xxx=zzz)
こちらは、少し違います。
プロセッサから見れば、配置の入れ替えは発生していません。
char bbb[4] が、
bbb[0] = 0x12; bbb[1] = 0x34; bbb[2] = 0x56; bbb[3] = 0x78;
であれば、bbb[0] と同じ番地から始まる4バイトを、「int として解釈すれば」 0x78563412 となるということです。
これを、0x12345678 という数値にしようとしているわけですから、この結果を、メモリに保存したときに、結果として配置が換わってしまうことになります。
ですから、あえて言えば、プロセッサのレジスタとメモリのやりとりの際の順番がそう決まっているのだということです。

> あと、こういったコピーを行う場合は、どちらが定石なのでしょうか?
(memcpyするのはありえない気がしてきました。)

これも、どういうデータを使うのか。例えば、bbb[0]は、下位桁なのか上位桁側なのかによって変わってきます。
配置が矛盾しなければ、memcpy() でいっこうにかまわないわけです。
(例えば、マッキントッシュに使われていたような、Power PC では、このケースでは、配置が同じです)
逆に配置が矛盾していれば、(シフトとマスクを使うかどうかは別としても)ご質問にあったような計算をして、データの並び(というか、バイト列の並び)を変更する必要があるわけです。

最近は、ネットワークやデータベース関連でバイナリデータを使う場合にこの問題が表面化します。特に、ネットワークではデータはバイトの列で流れてくるわけですが、相手のプロセッサは特定できませんから、いずれかのエンディアンで流す必要があります。そして、ネットワークに限らずストリームデータとしては、ビッグエンディアン(上記で言えば、Power PC の流儀)のほうが自然です。そのようなわけで、Pentium などでは、バイナリデータとして流れているものを、再構成しなければならないケースが、ままあると言うことです。
    • good
    • 0

この質問で、わざわざ環境として、CPUあたりを記述しているのは


バイトオーダーだと感づいていたからだと思いますので、
参考URLをどうぞ。

http://www.atmarkit.co.jp/icd/root/70/5784470.html
http://www.atmarkit.co.jp/icd/root/72/116970472. …
    • good
    • 0

それは、「エンディアン」というものの問題です。



32bit の int aaa = 0x12345678; に対して、&aaa = 0x1000 だとすると、

0x1000 : 0x12
0x1001 : 0x34
0x1002 : 0x56
0x1003 : 0x78
と配置するプロセッサと、
0x1000 : 0x78
0x1001 : 0x56
0x1002 : 0x34
0x1003 : 0x12
と配置するプロセッサがあります。
Pentium は、後者です。

一見、前者の方が自然な表現に感じられますが、後者の方は、例えば、
union
{ int intValue;
shot shorValue;
}
のようなものに対して、最下位の部分がそろうので、
intValue = 1 の時に、shotValue == 1 となるように、
同じアドレスから始まる整数データが、データ長の短い型の範囲で一致するので、都合がよいのです。

一方、char bbb[4]は、先頭番地が 0x2000 だとすると
0x2000 : bbb[0]
0x2001 : bbb[1]
0x2002 : bbb[2]
0x2003 : bbb[3]

と配置されます。memcopy は、これをこのままの配置でコピーします。
シフトを伴った方法では、aaa の値が、bbb[0] * 0x1000000 + bbb[1] * 0x10000 + bbb[2] * 0x100 + bbb[3] になりますから、これを、上述のいずれかの配置で配置します。
このため、Pentium 系列のプロセッサでは、メモリ上の配置が異なることになります。

あと、ご質問とは全く関係ないことですが、「お世話様です」という表現は、目上から目下に使うというニュアンスが感じられることがあります。
この言葉は、こういった場所ではあまり適切ではないと感じられます。

参考URL:http://ja.wikipedia.org/wiki/エンディアン
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
より理解を深めるために教えていただきたいのですが…。

配置のタイミングとしては以下の見解であってますでしょうか?
(1)アドレスを伴った間接代入時には、配置は行われない(memcpy)
(2)直接代入時に配置換えが行われる。(xxx=zzz)

あと、こういったコピーを行う場合は、どちらが定石なのでしょうか?
(memcpyするのはありえない気がしてきました。)

わかる範囲で結構です。よろしくお願いします。

お礼日時:2006/12/01 13:53

各 ccc =


の所と
aaa |=
の所で内容を一々確認すると 理解しやすくなるからやってごらん
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!