やりたかったこと
string.h
にあるmemcpy()関数を使って配列を希望の数だけコピーできるがこれを用いて大きな配列から適当な部分を切り出して保存したかった。
全てint型と仮定して例にだすと,
BUFF = {1,2,3,4,5,6,7,8} とあるならば,A={1,2,3,4},B={5,6,7,8}のように分けたい。
マルチバイト文字列をシリアル通信で送る時などに使うだろう。
正解(のうちの一つ)
配列Aに値を詰めるのは楽勝でBに詰めるのに詰まったのだが,
結論から言うと以下のように書くのが良いだろう。
memcpy(&a,&buff,sizeof(int)*4);
memcpy(&b,&buff[4],sizeof(int)*4);
&buff[4]でbuffの5番目の値の先頭のアドレスを指す。
コメントを頂いたが送り手と受けてが配列ないしポインタで定義されているなら以下の様に書き換えても構わない。
memcpy(a,buff,sizeof(int)*4);
memcpy(b,buff+4,sizeof(int)*4);
ポインタの加算にて失敗した話
C言語の授業でおぼろげにポインタを加算した記憶があった私は以下のコードでも動くだろうと最初に実装したがこれは間違いであった。
memcpy(&a,&buff,sizeof(int)*4);
memcpy(&b,&buff+4,sizeof(int)*4);
このコードの間違いは,buffについている+4の加算が変数buff
のサイズ×4つ分アドレスを移動してしまうという点にある。
今回,buffを8列*int = 32バイトと仮定したので(注:intのサイズは環境に依る)これは計128バイト,=0x80分も動いてしまう。
ポインタをint*でインクリメントするならintの大きさの分のみインクリメントされるが,今回のbuffはいわばポインタのポインタint**を指すことをうっかり忘れていたのだった。
再現コード
以下のコードを実行して未来の自分にはこの教訓を思い出してほしい。
#include<stdio.h>
#include<string.h>
int main() {
int buff[8]={1,2,3,4,5,6,7,8};
int a[4],b[4];
int* ptr;
memcpy(&a,&buff,sizeof(int)*4);
memcpy(&b,&buff[4],sizeof(int)*4);
printf("%d %d\n",a[0],b[0]);
ptr = &buff[0];
printf("array: %p %p %p\npointer: %p %p %p\n",&buff,&buff+1,&buff[4],ptr,ptr+1,ptr+4);
}
- 計算結果は以下の通り。
1 5
array: 0x7ffe69c9ff00 0x7ffe69c9ff20 0x7ffe69c9ff10
pointer: 0x7ffe69c9ff00 0x7ffe69c9ff04 0x7ffe69c9ff10
余談:マルチバイト列から浮動小数点の復元
今回の受けては同じint型だったが,uchar型で4つ値を受けてfloatで定義したアドレスに値を順次詰めることで浮動小数点を復元できる。
#include <stdio.h>
#include <string.h>
int main()
{
float f1,f2;
unsigned char bf[8],in1[4],in2[4];
float v1 = 9.8;
float v2 = 0.1;
memcpy(&in1,&v1,4);
memcpy(&in2,&v2,4);
for(int i=0;i<4;i++){
bf[i]=in1[i];
bf[i+4]=in2[i];
}
printf("%s \n",bf);
memcpy(&f1,&bf,4);
memcpy(&f2,&bf[4],4);
printf("f1 = %f, f2 = %f \n",f1,f2);
return 0;
}
ためしてみるよろし。