Page List

Search on the blog

2010年7月24日土曜日

memset()とmemcpy()

恥ずかしながら、Cのmemset()とmemcpy()を今まで使ったことがなかった。
memset()は欠点があるみたいな話を聞いたことがあったので、「あーじゃ要らない機能なんだー。。」
くらいに思っていた。

今回、TopCoder editorialに投稿された解答がmemset()、memcpy()を使っていたので改めて勉強しなおした。
これは、なかなか使えそう。。。

まずは、memset()について見てみよう。
ここで問題!あなたは普段、配列を初期化する時どうしていますか?

ちなみに、宣言時にすべての要素を0に初期化する場合は、これでOKです。(disp()は確認用に標準出力へ出力するためのコードです。以下同様。)


  1. #define forf(i, n) for(int i=0; i<(int)(n); i++)  
  2. #define disp(x) cout << #x << " : " << x << endl  
  3.   
  4. using namespace std;  
  5.   
  6. int main () {  
  7.  int x[10] = {0};  
  8.   
  9.  forf(i, 10)  
  10.   disp(x[i]);  
  11.  return 0;  
  12. }  


これは、まだ楽ですね。

じゃあ、宣言した後で、初期化するときは?

  1. int main () {  
  2.  int x[10];  
  3.   
  4.  forf(i, 10)  
  5.   x[i] = 0;  
  6.   
  7.  forf(i, 10)  
  8.   disp(x[i]);  
  9.  return 0;  
  10. }  


上記のようにやってる人。。。ちょっと面倒臭くないですか?
これを解決してくれるのがmemset()です。

  1. int main () {  
  2.  int x[10];  
  3.   
  4.  memset(x, 0, sizeof(x));  
  5.   
  6.  forf(i, 10)  
  7.   disp(x[i]);  
  8.  return 0;  
  9. }  


さてさて、では0ではなく1に初期化するときは?

  1. int main () {  
  2.  int x[10];  
  3.   
  4.  memset(x, 1, sizeof(x));  
  5.   
  6.  forf(i, 10)  
  7.   disp(x[i]);  
  8.  return 0;  
  9. }  

と思いきや、これを実行すると、16843009に初期化されてしまいます。ちょっと考えてみて気付きました。

16843009 = 1 + 2^{8} + 2^{16} + 3^{24}

なるほど、配列の要素を初期化するのではなく、メモリを1byte単位で初期化してるのね。。。うちのマシーンは、intが32bit(= 4 byte)なので、0x01010101に初期化されていたということです。
なるほど。。。

つなりこのmemset()という関数は、変数のサイズが1byteであるcharに使うってのが最も基本的な関数なようですね。。そうか!この要件を担保するためにcharのサイズは環境依存なしの1byteなのか!!(個人的な予想。。)

っと、ちょっと話が脱線しましたかね。
ではmemset()のさらに便利な使い方を。。2次元配列の初期化2重ループでやってませんか?そうです。これでイケちゃいます!!



  1. int main () {  
  2.  int x[10][10];  
  3.   
  4.  memset(x, 0, sizeof(x));  
  5.   
  6.  forf(i, 10)  
  7.   forf(j, 10)  
  8.    disp(x[i][j]);  
  9.  return 0;  
  10. }  

これでmemset()の便利さは分かりましたね!
さて、次はmemcpy()。細かい事はここまで読んでくれた人なら、多分分かってくれるはず。
memcpy()を使えば、以下のようにして2次元配列のコピーが可能です!

  1. int main () {  
  2.  int x[3][3];  
  3.  int y[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};  
  4.   
  5.  memcpy(x, y, sizeof(y));  
  6.  forf(i, 3)  
  7.   forf(j, 3)  
  8.    disp(x[i][j]);  
  9.  return 0;  
  10. }  



ちなみにmemcpy()の第3引数では、コピー元から何バイト分だけコピー先へコピーするかを指定します。
さー、ここまで来て、memset()、memcpy()はどうやらかなり出来るヤツらしいということが判明しました。では、これらの関数の欠点って何??
ちょっとネットで調べましたが、よさそうなページは見つけられませんでした。

自分なりに考えてみましたが、この問題点は、先に説明した1で初期化するつもりが0x01010101で初期化されるってことに関係してる気がします。
私が言いたいのは、intのサイズが32bitのマシンで意図的に配列の要素が0x01010101に初期化されるようなコードを書いた時に、これを16bitのマシンに移植すると、0x0101に初期化されることになる。
これが問題なのではないかな~~。と。。
自分ひとりで開発するなら問題ないけど、複数チームで複数環境で開発なんてやってるときに、この辺りを理解せずにむやみやたらにmemset()を使ってたら危険だぞ!ってことですね。

他に何か欠点をご存知の方いらっしゃいましたら、是非是非コメント頂ければ。。


0 件のコメント:

コメントを投稿