最近更新したページ
2013-10-20
2013-09-29
2013-09-23
2012-01-07
2011-11-09
2011-10-23
2011-10-09
2011-10-01
2011-09-29
2011-09-03
2011-08-07
2011-08-02
2011-07-29
2011-07-10
2011-05-05
2011-05-04
2011-04-24
2011-04-13
2011-04-05
2011-03-26
2011-02-18
2011-02-15
2010-12-26
2010-12-07
2010-12-05
2010-11-23
2010-09-28
2010-09-23
2010-08-26
2010-08-22
2010-07-16
2010-01-17
2010-01-11
2009-10-04
2009-08-21
2009-08-13
2009-06-18
2009-06-01
2009-04-29
2009-02-16
2009-02-11
2009-02-03
2008-07-22
2008-07-21
2008-07-15
2008-07-14
2008-07-13
2008-07-12
2008-07-08
2008-07-05
2008-06-28
2008-06-17
2008-06-05
2008-06-02
2008-06-01
2008-05-29
2008-05-26
2008-05-21
2008-05-19
2008-05-18
2007-10-31
2007-10-27
2007-09-28
2007-09-23
2007-09-17
2007-09-16
2007-09-14
2007-09-11
2007-06-18
2007-04-15
2006-12-21
2006-11-30
2006-11-22
2006-08-17
2006-03-29
2006-03-28
2006-03-27

C/C++ 配列処理


C/C++ 配列処理


 DLL作成も一段落。ようやく配列へ来た。(...と言うより、配列で躓いている...)
 C言語とC++ では、C++ が若干の機能拡張があるようなので注意が必要。

配列要素数を求める

 sizeof演算子を使用して、配列の要素数を求めることが出来る。但し、sizeof() は Build時に処理を行ってしまうらしく Build後は定数化するらしい。

配列の要素数取得(構造体メンバ)

 ・・・わからん!・・・>簡単に出来て驚く!
&dat で渡し、 *dat で受けたら問題なかった。
参考:表記方法のメモ
(*dat).name // sakura kinomoto
dat->name   // sakura kinomoto
dat[0].name // sakura kinomoto
// 何れも同じ
※配列渡し、それも構造体メンバの配列要素数を求めたい・・・が、頓挫状態・・・
※渡し側関数で要素数も別途値渡しすれば、何ら問題無いが、なんとも納得できない。
※動的配列の要素数取得も今後必要に迫られそうだが、ここで取り合えず頓挫。
sample2:
struct data {
   char   c;
   char   name[24];
   int    age;
   double sum;
   double arr[5];
};
double testSizeof(data* dat) {
   cout << "--------" << endl;
   cout << sizeof((*dat))        << endl; // 80
   cout << sizeof((*dat).c)      << endl; // 1
   cout << sizeof((*dat).name)   << endl; // 24
   cout << sizeof((*dat).age)    << endl; // 4
   cout << sizeof((*dat).sum)    << endl; // 8
   cout << sizeof((*dat).arr)    << endl; // 40 <---- サイズ取れてる?!
   cout << sizeof((*dat).arr[0]) << endl; // 8 40/8=5 配列サイズ 5
   cout << "--------" << endl;
   cout << (*dat).name           << endl; // sakura kinomoto
   cout << (*dat).sum            << endl; // 330
   cout << (*dat).arr            << endl; // 0012FF48
   cout << (*dat).arr[0]         << endl; // 70
   cout << (*dat).arr[1]         << endl; // 61
   cout << (*dat).arr[2]         << endl; // 84
   cout << (*dat).arr[3]         << endl; // 62
   cout << (*dat).arr[4]         << endl; // 53
   return 0.0;
}
void main() {
   data dat={'k',"sakura kinomoto",13,0,{70,61,84,62,53}};
   cout << sizeof(dat)      << endl; // 80
   cout << sizeof(dat.c)    << endl; // 1
   cout << sizeof(dat.name) << endl; // 24
   cout << sizeof(dat.age)  << endl; // 4
   cout << sizeof(dat.sum)  << endl; // 8
   cout << sizeof(dat.arr)  << endl; // 40
   testSizeof(&dat);
}
こんなんでいいか不明。特に代入方法とか・・・思いの他簡単に配列要素数が出た。
sample1:
//#pragma pack(push,1)
struct data {
   char   c;
   int    i;
   double d;
};
//#pragma pack(pop)
void main() {
   data dat;
   cout << sizeof(dat)   << endl; // 16 == 4(1->4へ拡張)+4byte+8byte
   cout << sizeof(dat.c) << endl; // 1  スタック=4 なら 4に拡張
   cout << sizeof(dat.i) << endl; // 4
   cout << sizeof(dat.d) << endl; // 8
}
VC++ 依存となるが pragma 指定で構造体サイズの調整が可能。push,1->dat==13 2->14 4->16 … となる。

配列の要素数取得(ポインタ使用)

 ・・・わからん!・・・
結局、ポインタは配列と同等の利用が可能であるが、配列:定数、ポインタ:変数として先頭アドレスを保持すること、ポインタが指し示す配列の先頭アドレスから配列要素数を推し量るのは、・・・無理?
double arrSizeof(double* arr) {
   cout << "size+3 = " << *arr+3       << endl; // 3<=arrI[3]
   cout << "size = "   << sizeof(arr)  << endl; // 4<=ポインタサイズ
   cout << "*size = "  << sizeof(*arr) << endl; // 8<=1データサイズ
   return 0.0;
}
void main(){
   double arrI[10]={0,1,2,3,4,5,6,7,8,9};
   arrSizeof(arrI);
}
 ちなみに・・・
double arrSizeof(double arr[]) { ・・・
 ・・・として受けても同じであった。・・・>「これでも動くんだ!」っと感心する始末。
※ポインタや参照でのエイリアス(別名)では、その先の配列要素数を sizeofで簡単に取得するのは、簡単に行かないようである。

二(多)次元配列の要素数取得

double arrD[3][3];
cout << sizeof(arrD[0][0]) << endl; //  8byte
cout << sizeof(arrD[0])    << endl; // 24=8*3
cout << sizeof(arrD)       << endl; // 72=8*9
printf("要素数 = %d\n",sizeof(arrD)/sizeof(arrD[0][0])); // 9
※多次元配列の要素数は、自ずと予想がつく。(未確認)

一次元配列の要素数取得

char arrC[10];
printf("sizeof(arrC[0] = %d\n",sizeof(arrC[0]));     // 1byte
printf("sizeof(arrC)   = %d\n",sizeof(arrC));        // 1*10=10
printf("要素数 = %d\n",sizeof(arrC)/sizeof(arrC[0])); // 10/1=10(要素数)
// -------------------------------------
int arrI[10];
printf("sizeof(arrI[0] = %d\n",sizeof(arrI[0]));     // 4byte
printf("sizeof(arrI)   = %d\n",sizeof(arrI));        // 4*10=40
printf("要素数 = %d\n",sizeof(arrI)/sizeof(arrI[0])); // 40/4=10
// -------------------------------------
double arrD[10];
printf("sizeof(arrD[0] = %d\n",sizeof(arrD[0]));     // 8byte
printf("sizeof(arrD)   = %d\n",sizeof(arrD));        // 80
printf("要素数 = %d\n",sizeof(arrD)/sizeof(arrD[0])); // 80/8=10

動的なメモリ管理


※「動的なメモリ管理」は「配列処理」から大分逸脱してきたため「C/C++ 動的なメモリ管理」のページを別途作成予定。

 C++/CLI なら、C# や VB のようにメモリ管理(ガベージコレクション)を自動でしてくれるようである。...が、.NET 上で動くことが前提の様なので「ネイティブコード」にこだわるなら C++/CLI は「期待外れ?」となるかも。(そもそも、配列要素数に変数が使えないこと自体、VB 他、言語利用の常識を根底から覆される。C/C++ が如何に機械寄りに作られているかを、ポインタも含め認識する必要がある。)

 C/C++ は、配列サイズ、使用メモリ領域サイズをコンパイル(Build)時に確定する。これでは、配列の宣言を通常利用するであろう最大サイズの 1.5~2.0倍程度確保しておかないと怖くて使えない。一昔前のオフコン用単一業務向けシステムじゃあるまいし無駄なメモリ確保には抵抗がある。(※資源(メモリ)が豊富なら安定すると思うが...)

 手間は掛かるが、プログラム実行時に必要メモリを確保して実行することができる。これを、動的配列、動的なメモリ管理と呼ぶ。

C/C++ 動的なメモリ割当て方法


言語 メモリ確保メモリ開放領域適用
C void *malloc(size_t size)
void *calloc(size_t nelements, size_t bytes)
void *realloc(void *pointer, size_t bytes)
void free(void *pointer)ヒープ関数
失敗=NULL
<stdlib.h>
C++ new 演算子 delete 演算子 ヒープ演算子
失敗=例外
<new>
bad_alloc
.cpp なら、new/delete 演算子を使用する。
.c なら malloc()/free() 等を使うしかない。※混在厳禁

▲上へ

C++ の動的メモリ確保/開放と例外処理


new/delete:動的配列のメモリ割当て
int size = 100;
double *p;
Try {
   p = new double[size]; // 動的メモリ割当:size は変数可。確保失敗=例外
   // 〜 処理実行 〜      // メモリ確保失敗 -> 例外キャッチ -> 例外処理
}
catch(bad_alloc) {
   // 〜 メモリ割当ての例外キャッチ 〜
   abort();
}
catch(...) {
   // 〜 それ以外の例外キャッチ 〜
   abort();
}
delete [] p;             // メモリ開放(破棄):開放必須
※例外処理を含めた利用が必須。

new は、メモリ割当てに失敗すると、通常 例外 bad_alloc を発生(コンパイラの旧バージョンでで NALL の場合有り)させる。Try 〜 catch で例外処理を行うことで例外処理を行いエラー回避が可能である。

new/delete 演算子と、Try~catch の例外処理は結構深いものがありそうで、簡単に纏めることが出来ない。例えば「複数の動的なメモリ確保」「動的メモリ使用中の例外発生」とか ... 何れもメモリリークを回避するロジックを組んでないとまずい。Try~catch も複雑にネストさせる必要も出るようである。(これらの処理(例外)のため、別関数を定義する必要さえ感じる...)

※注:abort(); と exit(1); の違いが良く解らない。
参考
http://msdn.microsoft.com/ja-jp/library/cc440190.a...

 メモリ確保の限界まで「メモリ取得」を繰り返したところ、HD のスワップを全部を使い切って例外発生。その間2回 Windows が「スワップ領域拡張」メッセージを出した。
 メモリ確保不能で catch(bad_alloc) { 〜 } へ移行したと思われるが、その後 delete してない(delete 入れてない)にも関わらず 勝手に開放されたような感じ?

※実メモリを含め 1.8G 程度が最大制限値となる環境で実施。
※ネイティブコード実行:急速にメモリ使用量が通常レベルへ減少確認。
※他の実行アプリへの影響は特に感ぜず...が、一応再起動して作業再開。

▲上へ

C言語の動的メモリ確保と開放例


※注:参考程度のメモ書き。動作チェック等はしていない。

malloc()/free():動的配列のメモリ割当て
// int p[10]; 同等の動的配列を確保する。
#include <stdlib.h>
int *p;
p = (int *) malloc(sizeof(int) * 10);
// int *p = malloc(sizeof(int) * 10);   // この一行でも同じ。
if (p == NULL) { exit(EXIT_FAILURE); }  // メモリ確保の成否を確認。
// 〜 処理実行 〜
free(p);    // メモリの開放。注:開放後のメモリアクセスをしないこと。

exit は、void exit(int status); 関数で、EXIT_FAILURE は、0(ZERO) 以外を持つ。(1 でも良い) exit(EXIT_FAILURE); は、プログラムを異常終了させる関数である。

▲上へ

配列の宣言


動的な配列宣言


 正確には、動的メモリの使用(ポインタ?)となり、厳密な意味での配列では無いらしい。

 また、実際の宣言時にメモリを確保するので、メモリの有効活用が可能。確保したメモリは、プログラマが開放する必要があり、放置すると使用不能(無駄な)メモリ領域を多数占有することになる。

 通常 C/C++ での配列宣言は、固定値を使用して配列要素数を固定し宣言する。これは、コンパイル時に配列のサイズが確定する仕様のようなので致し方ない。

 しかし、C++ では動的配列宣言の宣言がサポートされている。変数を使用した要素数の指定が可能となるが、new 演算子を使って領域確保(インスタンス作成ということか?)を行い、使用後は、delete 演算子を使い明示的に破棄する必要がある。

010:void dynamicArray(int size) {
020:
030:    double *p;
040:    p = new double[size];       // 動的メモリ割当
050:
060:/*
070:    〜 処理実行 〜
080:    イメージは、*(p+0)==p[0] 〜 *(p+n)==p[n] という感じ。
090:    p[n]=256; 等と使えるか不明だが、多分ダメと思われる。
100:    p[120]=50; として build したがエラーは出なかった。
110:    ※実動作は未確認。
120:*/
130:
140:    delete [] p;                // メモリ開放(破棄)
150:
160:}
※030:,040: の型名が一致しないと型不一致で、convert 不能の cast エラー発生。
※型名は、class の型も使用可能。
※new 演算子は、割り当てたメモリのポインタを返すので、ポインタで受ける。
※メモリ割当に失敗した場合、new 演算子は例外を返すので、例外処理でエラー回避する仕様として作成する。
※delete には、割り当てたポインタ変数を指定する。配列として割り当てた場合、[] も忘れずに付加する。

 VB2005 の動的配列宣言もこんな感じだったと思う(忘れた...)が、.NET Framework 以外のネイティブな実行ファイルも作れるのかは、現状不明...、C/C++ は、混在可能環境(VB もかなりアバウトな仕様だが、C/C++ も別の意味でかなりアバウトだと思う...)のようなので何とかなりそうではある…が...

※VB では、もっと配列っぽい仕様だったと思う。C++ のこの仕様は、どう見てもポインタである。
※C言語は、動的メモリ割当関数 malloc() と メモリ開放に free() がある。C++ では、new/delete 演算子が新たに設けられたため malloc() free() の使用は推奨されないらしい。

※C++ で、malloc(), free(), new, delete の混在は論外の禁じ手。

▲上へ

配列の宣言(通常)


 C/C++ の要素数を固定した、基本的な配列の宣言方法。
 コンパイル時に配列要素数が固定されるので、要素数指定は定数(変数使用不可)を用いる。

配列宣言適用
1double dArray[n];n個の配列要素(n は非変数)を宣言。
index(添字)は 0~n-1 となるが
C言語は配列要素数は定数を指定する。
※C++ では、動的配列が可能、この場合変数で要素数指定可。
1char strArr[25]=
{'A','B', ... '\0'}
25個の文字型配列宣言と同時に値を代入する。
1int a[]=
{10,20,30,40,50}
配列宣言と値の代入を同時に行う。
この場合要素数の省略が可能。
要素数=代入要素数となる。
この例では、a[5] と同じ。
1char strArr[]=
{"Hello!"}
{'H','e','l','l','o','!','\0'} と同じ。宣言時のみ "文字列" で初期化可能。
xint index=10;
double dArray[index];
要素数の変数指定は誤り。動的配列指定では、要素数の変数指定可能。
-----------
const int index=10;
double dArray[index];
-----------
#define ARRAY_INDEX 10
double dArray[ARRAY_INDEX];
-----------
...なら OK
2int x[3][3]=
{{0,1},{4,5,6},{7,}}
二次元配列。指定無は不定値
2int name[10][6]=
{"Andy","woody","bazz",...}
二次元配列を文字列で初期化。
5+1文字以内,10件登録可の例。
2int result[][3]=
{
{62,80,78},
{80,76,53},
{68,70,68}
}
要素数を省略した配列宣言と値の代入。
例:国,算,理の点数を持った配列など。
double dArr[5][6][10];何次元でも原則可能
1->一次元配列, 2->二次元配列, 多->多次元配列, x->使用不可

▲上へ

配列、ポインタ、動的メモリに関連するその他の項目


関連用語の覚書

※覚書のため内容非保証。

  • メモリーリーク
 メモリーリークとは動的に割り当てたメモリブロック(malloc() / new)が解放(free() / delete)されない問題のこと。これが積み重なると次第に使えるメモリ枯渇してしまう。
 主に、動的メモリの開放をせず、ポインタへ動的メモリを再割り当てた場合などに発生するが、プログラムの作成の仕方、動的メモリの管理不備で発生する。
 コンパイル、実行初期でエラーは発生しない。積み重なることで PC が動作不能となる場合も考えられる。長時間稼動するサーバーなどでは致命的と思われる。

  • 静的メモリ/自動メモリ・動的メモリ/ヒープ領域
    • 静的メモリ/自動メモリ
 プログラム実行中確保されつづけるメモリが静的メモリ。サブルーチンなどにコールスタックに確保されその実行時のみ存在するのが自動メモリ。何れもコンパイル時に利用メモリサイズが確定している。
    • 自動メモリ
 コンパイル時に使用メモリサイズが確定してしまう静的メモリ・自動メモリに対し、実行時に任意のサイズ、任意の期間確保し、その管理がプログラム側で管理できるメモリブロックを確保する手段として自動メモリがある。
 自動メモリは、ヒープ領域という場所にメモリを任意に確保して使用するが、その手段として C言語では、malloc()-free() 関数があり、C++ では new-delete 演算子がある。
      • メモリ確保エラーとメモリーリーク
 メモリーリークや確保不能なサイズを指定した場合、メモリ確保エラーが発生する。
 この場合、malloc は、NULL を、new は例外エラーを返す。適切なエラー処理を行わないプログラムは異常終了、最悪稼動システムに影響を及ぼす可能性も考えられる。

  • 開放したメモリ(ポインタ)の不正使用
 free などで開放したメモリ領域を、再メモリ確保せずに値を代入した場合、ポインタの指し示す領域が別の目的で既に利用されている場合もあるため、実際深刻な問題が発生する場合が考えられる。これは、free 後 再度 malloc せずにポインタのアドレスが利用できる C言語の仕様にも ... 思うがやっちゃぁイケナイ典型的プログラムミスと言える。

※このように見ると、やはり C/C++(...というよりポインタ...)は、アセンブラに近い爆弾要素を数多く抱えているのがよくわかる。
※.NET Framework の仕様(C++/CLI のことと思う)に則して作成する場合、ガベージコレクション(自動的に未使用メモリを探し出し開放する)とか、危険なプログラムが書けない(コンパイル時にエラーを出す?)仕様とか飼いならされた感がある。(それなら、VB や C# 使うが良いという気がする...)

▲上へ

リンク


内部リンク



外部リンク


  • 現在ありません

▲上へ

配列と無関係なメモ


 都度思いついたメモ・走り書き。

べき乗(累乗)について


 ちなみに、べき乗と階乗は全く異なる。(一時錯乱)

 2 の 2乗は、4 である。
 通常、4=2^2; で求められるが、C/C++ では異なる。
 C/C++ には、そもそも bit 計算というのが有り "^"(キャップ) を演算子とする場合、既に別用途予約済みのため期待値が戻らない。

C/C++ でべき乗を求める場合、double pow(double x,double y) を使用し...
#include <math.h>
double MathPow(double x,double y) {
    ans = pow(x,y);
    return ans;
}
...として求める。(math.h は必須)

※ちなみに、pow((複雑な計算式),y) としたら「多重定義関数に対する曖昧な呼出し…」などとコンパイルエラー。上記のように置き換えたところ解決。

▲上へ
2008年05月18日(日) 19:40:49 Modified by cafeboy1




スマートフォン版で見る