• ベストアンサー

p[-3][-4]にアクセス出来るようにしたい

ポインタのポインタをうまく使って、p[-3][-4]のようなアクセス(が有効に行われるように)したいのですが、うまくいきません。 ご教授願えないでしょうか。 以下のコードで p[-3][0] を a[0][0] を参照させる(?)ようにはできました。 後ろ側の添え字のマイナスシフトができないのです。 つまり p[-3][-4] が a[0][0] を参照させるようにするギミックが知りたいのです。よろしくお願いします。 ------------------------------------- int **a; // 領域の動的確保 ( [12][18] ) a = new int*[12]; for(int i = 0; i < 12; i++) a[i] = new int[18]; int **p; p = &(a[3]) ; for (int y=0; y<12;y++) for (int x=0; x<18;x++) a[y][x] = 1; p[-3][0] = 99; //a[0][0]が99になる。 //領域の解放 for(int i = 0; i < 12; i++) delete [] a[i]; delete [] a;

質問者が選んだベストアンサー

  • ベストアンサー
  • jacta
  • ベストアンサー率26% (845/3158)
回答No.3

どうしてもp[-3][-4]がa[0][0]を参照するようにしたいのでなければ、次のようにしてはどうでしょう。 ------------------------- int **a; // 領域の動的確保 ( [12][18] ) a = new int*[12]; for(int i = 0; i < 12; i++)  a[i] = (new int[18]) + 4; // ← 変更点 int **p; p = &(a[3]) ; for (int y=0; y<12;y++)  for (int x=0; x<18;x++)   a[y][x] = 1; p[-3][0] = 99; //a[0][0]が99になる。 //領域の解放 for(int i = 0; i < 12; i++)  delete [] (a[i] - 4); // ← 変更点 delete [] a; ------------------------- 上のコードでは、p[-3][-4]がa[0][-4]を参照することになりますが、目的は満たせるのではないでしょうか。

lachesis-r
質問者

お礼

素晴らしいです。感動しました。 こんなに早く目的通りの回答をいただきありがとうございました。 (念のためもう1日程待ってから閉めたいと思います。)

すると、全ての回答が全文表示されます。

その他の回答 (10)

  • alfsan
  • ベストアンサー率0% (0/2)
回答No.11

ポインタのポインタをうまく使って・・の項目が絶対でなければ ポインタを使わずに、そこのindexをとったほうがわかりやすくてスピードもそんなに落ちないと思うのですが、どうでしょうか int **p; -> int pX, pY; p = &(a[3]) ; -> pX=3;pY=4; p[-3][0] = 99; //a[0][0]が99になる。 -> pX-=3;pY-=4;a[pX][pY]=99; あと、c++では普通動的配列なら、標準のvectorをオススメしておきます。 最適化がきけば、普通の配列と同じ速度でアクセスできるらしいので、今回のような用途でも大丈夫だと思います 使い方はこんな感じ #include <stdio.h> #include <vector> int main(){ using namespace std; vector< vector<int> > table(12);//vector<int>が12個入ったvecor<vector<int> >を作る for(int i = 0; i < table.size(); i++){ table[i].resize(18,i);//各要素vector<int>を、サイズが18の値が全てiのintで初期化する } for(int y=0;y<table.size();y++){ for(int x=0;x<table[y].size();x++){ printf("%d,",table[y][x]);//表示してみる } } return 0;//開放は自動でやってくれる }

lachesis-r
質問者

お礼

回答ありがとうございます。 vectorはbuilderのコンパイラでは普通に使っても速度的に不利なことが分かっていますので(最初に確認済み)どうしてもアルゴリズムを使いたい場合に限定されます。 (インテルコンパイラだとよいのかもしれませんね) 最初の例は、クラスの内部でアドレスをオフセットするのと同じですね? builderのコンパイラは加算部分を常には最適化できないようで、着目アドレス以外(隣とか)を同時にアクセスすると遅くなるようです。(きちんと確認できていませんが)

すると、全ての回答が全文表示されます。
  • jacta
  • ベストアンサー率26% (845/3158)
回答No.10

少しだけチャチャを入れます。 > 恥ずかしながらreinterpret_castというのは初めて見ました。 reinterpret_castでオブジェクト型へのポインタを他のオブジェクト型へのポインタ型に変換した場合、(境界調整の条件さえクリアしていれば)元のポインタ型に再変換しても値が変わらないことが保証されているだけで、それ以外は未規定(要するに不定)になります。 T x; U* p = reinterpret<U*>(&x); assert(reinterpet_cast<T*>(p) == &x); // OK reinterpret_castは整数型とポインタ型の間の相互変換等には使えますが、それ以外は案外使い道がありません。(現物合わせで動けばOKなら使えますが...)

lachesis-r
質問者

補足

c++について勉強不足が露呈しました^^; 理解する前にまず使ってきているので基礎ができていませんね。反省です。 テンプレート?も使った経験がないので、機会があればトライしてみます。

すると、全ての回答が全文表示されます。
noname#30727
noname#30727
回答No.9

#5です。 >このテクニックは、2次元配列(に見える1次元配列)が隙間無く並んでいる事を利用してるんですよね? 仮に1次元単位の先頭(何と呼んだらいいのだろう)でアラインメントされたとしても特に影響無いですから、何かを利用したテクニックと言えるような大層なものではないです。 >2次元配列から1次元配列にアドレス変換するときに乗算が発生するのでテーブルの方が早いと思いますがいかかでしょうか? テーブル参照が無い方が有利というのは不用意な発言でした。 アクセス毎にインデックスからアドレス変換するのであれば、テーブル参照の方が有利な事もあると思います。 しかし、ループさせて計算する場合などは、最適化によってアドレス変換からポインタの移動に置き換えられる事も多いですし、縦方向の移動も単純な加減算で解決できます。 計算内容やアーキテクチャと処理系の違いで結果が変わりそうです。 >reinterpret_castというのは初めて見ました。 C++スタイルのキャストを使うと、キーワードに色を付けられるエディタで注意しやすくなっていいですよ。 (そんな理由で推奨するのを他で聞いた事は無いですが・・・)

lachesis-r
質問者

お礼

そうですね。ループ内で注目座標だけにアクセスする場合、コンパイラはアドレスを1づつデクリメントするものに変えてくれてもよさそうですよね! builderのコンパイラは2重ループの場合にはそこまでできない?ようです。速度的には不利でした。 もうちょっと色々試してみないと総合評価はできないですが、1次元配列も選択肢として残すべきですね。 ありがとうございました。

すると、全ての回答が全文表示されます。
  • xcrOSgS2wY
  • ベストアンサー率50% (1006/1985)
回答No.8

#2,#4回答者です。 pと同じ性質(qが配列へのポインタであって、qに対する添字に-3が使用できること)とは、すなわち 1. 配列を確保し 2. その配列に対してq[-3]のような形でアクセスできるようにqを初期化する ということです。 その上でq[n] = p[n] + 4を行うと、結果としてq[n]の内容は、#3でのp[n]の内容と同じになります。

lachesis-r
質問者

お礼

補足ありがとうございます。 やはり別に領域を確保しないといけないのですね。 今すぐは確認できませんが(^^; ちょっと試行してみます。

すると、全ての回答が全文表示されます。
  • sha-girl
  • ベストアンサー率52% (430/816)
回答No.7

えっ?って感じで質問をみましたが、回答の内容をみて関心しました。 ちなみにC++ならclassで[]演算子をオーバーライドできるので コード上p[-3][-4]でアクセスする事自体は簡単にできます。 >※6行目はコンパイルは通りますが実行時に不正なポインタ演算エラーが発生します。( int **q q[-3] = p[-3] + 4; を p[-3] = p[-3] + 4; とでもしたのではないですか? そうするとnewしたポインタの位置が書き換わってしまうので、deleteが失敗します。

lachesis-r
質問者

補足

No.4で補足しました。 まず「qが配列へのポインタであって、qに対する添字に-3が使用できること」の条件が満たせていないようです。

すると、全ての回答が全文表示されます。
  • Interest
  • ベストアンサー率31% (207/659)
回答No.6

ANo.1 です。 本音は、「職人芸的な技に感動したので自分も使ってみたかった」ということなのですね。これはあくまで私の考えですが、もう職人芸的プログラムを書く人の時代ではないと思います。職人芸より、他人が読んでさっと理解できる、誤解されないプログラムがよいプログラムなのだと。 言語仕様からすると文法的に間違いでなくても、普通の人がやらないようなことをやるのは、バグを自ら埋め込むことに近く、危険な行為ではないでしょうか。 > ある種の処理(フィルタ)では配列の外側が仮想的にあると非常にシンプルに記述できます。 > クラスにして添え字をずらすとオーバーヘッドが生じます(多分)ので、何千万回も処理する場合には効いてきます。 処理速度を上げたいのであれば、まずは最大のボトルネックを探し出し、必要とあらばアルゴリズムを替えるのが王道だと思います。添え字をずらす程度の処理は、コンパイラの最適化オプションで吸収されてしまうのではないでしょうか。そういうわけで、技巧に走らず、素直に実装なさることをお勧めします。

lachesis-r
質問者

補足

仰るとおり趣味の領域なのかもしれません。 私はc++builderしか使えないので、他の環境ではなんとも言えませんが、builderのコンパイラ、pentiumの命令スケジューリング、どちらも極めて優秀で、大抵は自分で最適化したつもりでもあまり早くなりません。 但し今回のケースでは確実に速度の改善が見られるようです。 フィルタ処理がほとんどなので配列の参照が多く配列の外側を参照するかどうかのチェックはオーバーヘッドになります。 クラスにして内部でずらすと早くなりますが、それでも下記のテクニックよりはかなり遅いです。(試しました) 実際にはフィルタ処理を早くさせるアルゴリズムは見つけてあるのですが、こちらの方は難解でデバッグが大変なので、使わないようにしているのです。 仮想的にアドレスをずらすだけなら、枠組みを用意するだけですし、枠組みの設計段階でデバッグをきちんと行えば後は楽です。

すると、全ての回答が全文表示されます。
noname#30727
noname#30727
回答No.5

ポインタへのポインタを使うという条件は満たせないですが、オーバーヘッドを気にするなら、ポインタテーブルが無い方が若干有利かな。 自分で言うのもなんですが、怪しいコードですね(笑 int (*a)[18] = new int[12][18]; a[0][0] = 1; int (*p)[18] = reinterpret_cast<int(*)[18]>(&a[3][4]); p[-3][-4] = 99; std::cout << a[0][0] << std::endl; delete[] a;

lachesis-r
質問者

お礼

回答ありがとうございます。 動作確認しました^^ すごいです。 恥ずかしながらreinterpret_castというのは初めて見ました。 まだまだ研鑽が足りません。 このテクニックは、2次元配列(に見える1次元配列)が隙間無く並んでいる事を利用してるんですよね? 2次元配列から1次元配列にアドレス変換するときに乗算が発生するのでテーブルの方が早いと思いますがいかかでしょうか? (以前試してみたことがあります。幅が2^nで無い場合にはやや遅くなったと思います)

すると、全ての回答が全文表示されます。
  • xcrOSgS2wY
  • ベストアンサー率50% (1006/1985)
回答No.4

#2回答者です。ううっ恥かしい・・・ 指摘のとおり、「p = a - 3」は「p = a + 3」の誤りです。 同様に q[-3] = p[-3] - 4; も q[-3] = p[-3] + 4; の誤り。 なお、後者はqに関して「qが配列へのポインタであって、qに対する添字に-3が使用できること」、つまりpと同じ性質が必要です。

lachesis-r
質問者

お礼

補足ありがとうございます。 「qが配列へのポインタであって、qに対する添字に-3が使用できること」の部分がよくわかっていないようです(汗 int **q; と用意して、 q[-3] = p[-3] + 4; としてもダメなのですね。 qも領域を確保しないとまずいのでしょうか。

すると、全ての回答が全文表示されます。
  • xcrOSgS2wY
  • ベストアンサー率50% (1006/1985)
回答No.2

一般にp[x]と*(p + x)は同じものの違う書き方に過ぎませんので、p[-3]とa[0]が同じものを指すようにするには p = a - 3; としておけば済みます。 さて、既にp[-3]とa[0]が同じものを指すようにはできたのですよね。つまり、p[-3][0]とa[0][0]も同じものを指すように、です。 このとき、q[-3][-4]とp[-3][0]が同じものを指すようにするには、最初の例を適用して q[-3] = p[-3] - 4; としておけばよいことになります。 つまり、pと同じ構造の配列をもう1つ用意し、そのそれぞれの要素として格納するポインタの値をずらすわけです。

lachesis-r
質問者

補足

早速の回答ありがとうございます。 ※2行目は p=a+3 ですね? ※6行目はコンパイルは通りますが実行時に不正なポインタ演算エラーが発生します。(c++builder使用) 参照するポインタのずらし方として正しいものはありませんか?

すると、全ての回答が全文表示されます。
  • Interest
  • ベストアンサー率31% (207/659)
回答No.1

ポインタの仕組みはご存知の上での質問と思いますが、なぜわざわざそのような危険なことをしたいのでしょうか? new, delete をお使いのようですが、C++ を想定されているのですか? C++ならば、クラスを作って get(x,y), set(x,y) のようなアクセス用メンバ関数を用意し、関数の中で配列にアクセスする数字を修正するという手はありそうですね。 とにかく、なぜそのようなことをしたいのか教えてください。

lachesis-r
質問者

補足

奥村晴彦著「C言語による最新アルゴリズム事典」で1次元の配列に巧妙にp[-5]のようにアクセスしていますので、イリーガルというわけではないのだと思います。 (私のようにポインタが不確かなまま使うのはリスキーですが) ある種の処理(フィルタ)では配列の外側が仮想的にあると非常にシンプルに記述できます。 クラスにして添え字をずらすとオーバーヘッドが生じます(多分)ので、何千万回も処理する場合には効いてきます。 (本当は上記アルゴリズム事典を見て感動したからなんですが)

すると、全ての回答が全文表示されます。

関連するQ&A