Help us understand the problem. What is going on with this article?

配列とポインタの加算でハマった話

More than 1 year has passed since last update.

やりたかったこと

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;
}

ためしてみるよろし。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away