none
配列の再拡張の仕方をお教え下さい。 RRS feed

  • 質問

  • Code Snippet

     case EMR_LINETO:

    //Polylineの終点を取得
    x = lpEMFR->dParm[0];
    y = lpEMFR->dParm[1];

     

    //出力用(兼WMF変換用)のEMFにPolyline命令を書き込み
    EMRPOLYLINE empoly;
    empoly.emr.iType = EMR_POLYLINE;       //レコードの種類にPolylineを指定
    empoly.emr.nSize = sizeof(EMRPOLYLINE) + sizeof(POINTL); //レコードのサイズ(バイト単位)
    empoly.rclBounds.left   = rl;        //Polyline描画の基準になる四角形の左端値(デバイス単位)
    empoly.rclBounds.top    = rt;        //上端値(デバイス単位)
    empoly.rclBounds.right  = rr;        //右端値(デバイス単位)
    empoly.rclBounds.bottom = rb;        //下端値(デバイス単位)
    empoly.cptl = 2;           //Polyline描画に用いる点の数
    empoly.aptl[0].x=ox;          //1点目x座標
    empoly.aptl[0].y=oy;          //     y座標
    empoly.aptl[1].x=x;           //2点目x座標
    empoly.aptl[1].y=y;           //     y座標
    PlayEnhMetaFileRecord(hDC , lpHTable , (ENHMETARECORD FAR *)&empoly , nObj);

    //Polylineの始点を取得
    ox = x;
    oy = y;
    return TRUE;
    break;

     

    上記のようにして,EMFのLineToレコードをPolylineレコードに置き換えるコードを書いていたのですが,

    VC++6.0ではこれで動いていたのに,VC++2008で動かしてみたら,このempolyを使用しているスコープが終了するときにスタックエラーが発生します。

    Code Snippet

    empoly.aptl[1].x=x;           //2点目x座標
    empoly.aptl[1].y=y;           //     y座標

     

    をコメントアウトするとエラーは発生しません。

    EMRPOLYLINE 構造体の定義が

    Code Snippet

    typedef struct tagEMRPOLYLINE
    {
        EMR     emr;
        RECTL   rclBounds;          // Inclusive-inclusive bounds in device units
        DWORD   cptl;
        POINTL  aptl[1];
    }

    となっていたので,むしろ今までちゃんと動いていた方が不思議なような気もします。

    VC++2008でちゃんと動かすには一番上のコードの中でaptlの配列を[2]に再拡張しなくてはならないと思うのですが,

    具体的にどうコーディングしたらよいのか,色々検索して試行錯誤しても分かりません。

    非常に初心者的質問で申し訳ないのですが,お答えいただけると非常に助かります。宜しくお願いします。

    2008年6月2日 2:43

回答

  • このテの構造体は、必要なメモリを確保した後で cast して使います。

    コンパイル時にサイズが決まっているなら、スタック領域を利用する方法がお手軽です。

     

    BYTE buf[sizeof(EMRPOLYLINE) + sizeof(POINTL)];

    PEMRPOLYLINE pEmpoly = reinterpret_cast<PEMRPOLYLINE>(buf);

    2008年6月2日 3:19
  •  monger_monger さんからの引用
    お騒がせいたしました。静的配列を使う場合は,2行目のbufの前に"&"を追加すればOKでした。

     

    PEMRPOLYLINE pEmpoly = reinterpret_cast<PEMRPOLYLINE>(&buf);

     

    ということでしょうか?

    これで OK になるというのは、それはそれでヘンだと思います。

     

     monger_monger さんからの引用
    newとdeleteの使い方はこれで大丈夫でしょうか。何かおかしな所があったらご指摘願います。

     

    途中で例外が発生しなければ大丈夫です。

    逆に言えば、new と delete の間で例外が発生してしまうと buf が開放されませんので注意が必要です。

     

    例えば以下のように vector を使えば、途中の例外についてあれこれ悩まなくてもよくなります。

     

    vector<BYTE> buf(sizeof(EMRPOLYLINE) + sizeof(POINTL));

    PEMRPOLYLINE pEmpoly = reinterpret_cast<PEMRPOLYLINE>(&buf[0]);

     

    この方法だと、buf の中身は最初からゼロクリアされているというオマケつきです。

    2008年6月2日 7:59

すべての返信

  • このテの構造体は、必要なメモリを確保した後で cast して使います。

    コンパイル時にサイズが決まっているなら、スタック領域を利用する方法がお手軽です。

     

    BYTE buf[sizeof(EMRPOLYLINE) + sizeof(POINTL)];

    PEMRPOLYLINE pEmpoly = reinterpret_cast<PEMRPOLYLINE>(buf);

    2008年6月2日 3:19
  • zakioさま,お返事有り難うございます。初歩的なことで申し訳ないのですが,更に質問させて下さい。

    Code Snippet

      BYTE buf[sizeof(EMRPOLYLINE) + sizeof(POINTL)];
      PEMRPOLYLINE pEmpoly = reinterpret_cast<PEMRPOLYLINE>(buf);

      pEmpoly->emr.iType = EMR_POLYLINE;       //レコードの種類にPolylineを指定

     

    とした場合,3行目で「保護されているメモリに読み取りまたは書き込み操作を行おうとしました。他のメモリが壊れていることが考えられます」というエラーが出てしまいます。

    1時間ちょっとあれこれしてみましたが,解決できませんでした。

    ダイアログベースでexeを作ってその中(OnOK)で上記コードを実行しても同様のエラーでした。

    解決策をご教示いただければ幸いです。

    2008年6月2日 5:37
  • お騒がせいたしました。静的配列を使う場合は,2行目のbufの前に"&"を追加すればOKでした。

     

    また,下記コードのように,動的配列を利用することでも意図する結果が得られました。

    Code Snippet

    {
      BYTE *buf = new BYTE[sizeof(EMRPOLYLINE) + sizeof(POINTL)];
      memset(buf,0,sizeof(EMRPOLYLINE) + sizeof(POINTL));
      PEMRPOLYLINE pEmpoly = reinterpret_cast<PEMRPOLYLINE>(buf);

     

      pEmpoly->emr.iType = EMR_POLYLINE;       //レコードの種類にPolylineを指定
      pEmpoly->emr.nSize = sizeof(EMRPOLYLINE) + sizeof(POINTL); //レコードのサイズ(バイト単位)
      pEmpoly->rclBounds.left   = rl;        //Polyline描画の基準になる四角形の左端値(デバイス単位)
      pEmpoly->rclBounds.top    = rt;        //上端値(デバイス単位)
      pEmpoly->rclBounds.right  = rr;        //右端値(デバイス単位)
      pEmpoly->rclBounds.bottom = rb;        //下端値(デバイス単位)
      pEmpoly->cptl = 2;           //Polyline描画に用いる点の数
      pEmpoly->aptl[0].x=ox;          //1点目x座標
      pEmpoly->aptl[0].y=oy;          //     y座標
      pEmpoly->aptl[1].x=x;          //2点目x座標
      pEmpoly->aptl[1].y=y;          //     y座標
      PlayEnhMetaFileRecord(hDC , lpHTable , (ENHMETARECORD FAR *)pEmpoly , nObj);

     

      delete[] buf;
      }

    newとdeleteの使い方はこれで大丈夫でしょうか。何かおかしな所があったらご指摘願います。

    2008年6月2日 6:27
  •  monger_monger さんからの引用
    zakioさま,お返事有り難うございます。初歩的なことで申し訳ないのですが,更に質問させて下さい。

    Code Snippet

      BYTE buf[sizeof(EMRPOLYLINE) + sizeof(POINTL)];
      PEMRPOLYLINE pEmpoly = reinterpret_cast<PEMRPOLYLINE>(buf);

      pEmpoly->emr.iType = EMR_POLYLINE;       //レコードの種類にPolylineを指定

     

    とした場合,3行目で「保護されているメモリに読み取りまたは書き込み操作を行おうとしました。他のメモリが壊れていることが考えられます」というエラーが出てしまいます。

    1時間ちょっとあれこれしてみましたが,解決できませんでした。

    ダイアログベースでexeを作ってその中(OnOK)で上記コードを実行しても同様のエラーでした。

    解決策をご教示いただければ幸いです。

     

    アラインメントのことをすっかり忘れてました。

    細切れの BYTE を無理やり DWORD にする際に、気まずい境界をまたいでしまったのかも知れません。

    ヘンな例を挙げて時間を無駄にさせてしまい、申し訳ありません。

     

    # サイズの指定も間違ってましたので、こちらは投稿の方を編集しておきました。

     

    2008年6月2日 7:38
  •  monger_monger さんからの引用
    お騒がせいたしました。静的配列を使う場合は,2行目のbufの前に"&"を追加すればOKでした。

     

    PEMRPOLYLINE pEmpoly = reinterpret_cast<PEMRPOLYLINE>(&buf);

     

    ということでしょうか?

    これで OK になるというのは、それはそれでヘンだと思います。

     

     monger_monger さんからの引用
    newとdeleteの使い方はこれで大丈夫でしょうか。何かおかしな所があったらご指摘願います。

     

    途中で例外が発生しなければ大丈夫です。

    逆に言えば、new と delete の間で例外が発生してしまうと buf が開放されませんので注意が必要です。

     

    例えば以下のように vector を使えば、途中の例外についてあれこれ悩まなくてもよくなります。

     

    vector<BYTE> buf(sizeof(EMRPOLYLINE) + sizeof(POINTL));

    PEMRPOLYLINE pEmpoly = reinterpret_cast<PEMRPOLYLINE>(&buf[0]);

     

    この方法だと、buf の中身は最初からゼロクリアされているというオマケつきです。

    2008年6月2日 7:59
  •  zakio さんからの引用

    アラインメントのことをすっかり忘れてました。

    細切れの BYTE を無理やり DWORD にする際に、気まずい境界をまたいでしまったのかも知れません。

    BYTE buf[sizeof(aa) + sizeof(bb)]が細切れ(のBYTE配列)になるんでしょうか?(この一文において、aaとbbはそれぞれ何らかの型とします)

    sizeof演算子の結果及びこの足し算自体はコンパイル時点で定数になり、連続した配列になると考えました。

    もし、間違いや捉え違いでしたら突っ込んでいただければ助かります。

    2008年6月2日 15:57
    モデレータ
  • zakioさま,返信ありがとうございます。

     

    PEMRPOLYLINE pEmpoly = reinterpret_cast<PEMRPOLYLINE>(&buf);

    でOKだったのですが,念のため

    PEMRPOLYLINE pEmpoly = reinterpret_cast<PEMRPOLYLINE>(&buf[0]);

    に修正しました。こちらでも正しく動作します。

     


    #include<vector>

    //--------------------------------------------------

    using namespace std;

    vector<BYTE> buf(sizeof(EMRPOLYLINE) + sizeof(POINTL));

    PEMRPOLYLINE pEmpoly = reinterpret_cast<PEMRPOLYLINE>(&buf[0]);

     

     

    こちらも試してみました(青字の追加が必要でした)が,正しく動作しているようです。

    ご助言ありがとうございました。

    2008年6月3日 4:21
  •  Azulean さんからの引用
    BYTE buf[sizeof(aa) + sizeof(bb)]が細切れ(のBYTE配列)になるんでしょうか?(この一文において、aaとbbはそれぞれ何らかの型とします)
    sizeof演算子の結果及びこの足し算自体はコンパイル時点で定数になり、連続した配列になると考えました。

     

    ちょっと表現が悪かったです。
    「細切れ」というよりは「BYTE の寄せ集め」といったところでしょうか。
    おっしゃるとおり、buf はメモリ上では連続しています。

     

    ただ、buf は単に連続しているだけで、先頭のアドレスが(例えば4の倍数というような)キリのいい場所にあることは保証されなかったはずです。(逆に new や malloc で動的に確保した領域は何らかの保証があったはず。)
    さらに、環境によっては int や double のような多バイトのデータがキリのよいアドレスに配置されていないと憤死してしまう恐れがあります。

     

    例えば、buf が運よく4の倍数アドレスに配置されたなら、

     

    0000: buf[0] <--- ここから
    0001: buf[1]
    0002: buf[2]
    0003: buf[3] <--- ここまでを int として扱う
    -----------
    0004: buf[4]
       :

     

    それを無理やり int (4byte とする) と見なしても大丈夫なのですが、以下のように buf の位置がズレると int が気まずい場所に配置されてしまうことになります。

     

    0001: buf[0] <--- ここから
    0002: buf[1]
    0003: buf[2]
    -----------
    0004: buf[3] <--- ここまでを int として扱う
    0005: buf[4]
       :

     

    ですから、buf だと失敗するのに &buf や &buf[0] でセーフだった事が少々気にかかっています。
    & が付いたことでコンパイル時に何らかの調整が入ったということなのでしょうか?
    それともコンパイルオプション等に依存するのでしょうか?
    残念ながら手元の環境(Core2, XP, VS2005)では再現しませんでした。

     

    2008年6月3日 10:08
  •  zakio さんからの引用

    さらに、環境によっては int や double のような多バイトのデータがキリのよいアドレスに配置されていないと憤死してしまう恐れがあります。

    「環境による」という範囲が広く普及しているPCで起こるのでしょうか?(もしくは、ごく限られた環境?)

    情報源をご提示頂けると助かります。

     

    私見:

    広く起こりえるとすると、pack(1)とされている範囲で宣言される構造体は非常に危険ですし、また、newやmallocで確保したBYTE配列の1番目から、例えば&buf[1]のようなところから構造体を割り当てた場合に動作がおかしくなります。

    容易に起こりえる状況だと思いますが、広く危険視されていないように感じます。

    2008年6月3日 14:20
    モデレータ
  •  Azulean さんからの引用
    「環境による」という範囲が広く普及しているPCで起こるのでしょうか?(もしくは、ごく限られた環境?)

     

    私の理解は、

     

    ・x86 なら大丈夫 (読み書きの時間に影響する)
    ・他のアーキテクチャは要注意 (x86 の方が少数派)

     

    といった程度です。(誰かフォローをお願いします)
    # ちょっと前に W-ZERO3 のソフトを書いた時にはアラインメントの事を気にしたような気がするので、ARM は多分駄目です。

     

    世の中で広く普及しているのは x86 アーキテクチャの PC だと思いますので、アラインメントに気をつけなければならない環境というのは少数派ということになるのでしょうか?
    でも Windows CE とか Mobile の世界になると、とたんに x86 以外の CPU が登場してきますので、普段から気をつけておくに越したことはないと思います。

     

    # XP や Vista って x86 以外のアーキテクチャに対応してましたっけ?

    2008年6月3日 16:23
  • ARM のマニュアルを斜め読みしたところでは、アラインメントのエラーチェックはフラグによって制御できるようなつくりになっているようです。
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0100i/index.html

    > [Non word-aligned addresses]
    > For CP15_reg1_Ubit == 0 the store coprocessor register instructions ignore the least
    > significant two bits of address. For CP15_reg1_Ubit == 1, all non-word aligned
    > accesses cause an alignment fault.

    それから、「アラインメント」をキーワードに検索をかけると結構引っかかりました。
    皆さん色々苦労されているようです。

    2008年6月3日 18:53
  • 動的に確保した領域のアドレスは ISO/IEC 14882:2003 で以下のように保証されていました。

     

    3.7.3.1 Allocation functions [basic.stc.dynamic.allocation] - 2 -

    ... The pointer returned shall be suitably aligned so that it can be converted to a

    pointer of any complete object type and then used to access the object or array

    in the storage allocated ...

     

    返ってきたポインタはうまい具合に位置合わせされてるから、

    どんなオブジェクトのポインタに変換しても大丈夫だよ (超簡訳 by zakio)

    2008年6月4日 2:08
  • zakioさま,Azuleanさま。

    >ですから、buf だと失敗するのに &buf や &buf[0] でセーフだった事が少々気にかかっています。

     

    今日テストしてみたらbufでも正常に動作しました。

    昨日は色々いじくりすぎて,何かがおかしかったのかもしれません。

    Windowsの内部的なものなのか,ソースコードの打ち間違いがあったのかは分かりませんが……。

    余計な混乱を招いたようで申し訳ありませんでした。

    (ちなみに開発環境は Athlon64 + VistaBusiness32 + VS2008Pro です。)

    2008年6月4日 4:22
  •  monger_monger さんからの引用
    今日テストしてみたらbufでも正常に動作しました。

    昨日は色々いじくりすぎて,何かがおかしかったのかもしれません。

    Windowsの内部的なものなのか,ソースコードの打ち間違いがあったのかは分かりませんが……。

    余計な混乱を招いたようで申し訳ありませんでした。

    (ちなみに開発環境は Athlon64 + VistaBusiness32 + VS2008Pro です。)

     

    確認、ありがとうございました。

    その環境で正常動作なら納得です。

    2008年6月4日 9:30
  •  zakio さんからの引用

    ・x86 なら大丈夫 (読み書きの時間に影響する)
    ・他のアーキテクチャは要注意 (x86 の方が少数派)

    なるほど。

    Mobile系では起こりうると言うことで理解しました。

    情報ありがとうございます。

     

     zakio さんからの引用

    世の中で広く普及しているのは x86 アーキテクチャの PC だと思いますので、アラインメントに気をつけなければならない環境というのは少数派ということになるのでしょうか?
    でも Windows CE とか Mobile の世界になると、とたんに x86 以外の CPU が登場してきますので、普段から気をつけておくに越したことはないと思います。

    何でもかんでもBYTEの配列に詰めるなとは言えますね。

     

    ただ、普段から気をつけてコードを書くと言うよりは、そういう環境もあり得るという認識だけ持っておくようなことが多くなるのかなと考えています。PCアプリしか組まないような会社もあるわけですし。

    もちろん、プラットフォーム間で行き来するようなコードを書く人は注意が必要になることには理解しています。

     

     zakio さんからの引用

    # XP や Vista って x86 以外のアーキテクチャに対応してましたっけ?

    x64は似たり寄ったりといえばそうですけれども、一応挙げてみますか。

    2008年6月4日 15:13
    モデレータ
  •  Azulean さんからの引用
    ただ、普段から気をつけてコードを書くと言うよりは、そういう環境もあり得るという認識だけ持っておくようなことが多くなるのかなと考えています。PCアプリしか組まないような会社もあるわけですし。

     

    そうですね。私ならコメントを入れる程度で済ませてしまうかも知れません。
    ただ、「危ないのは分かってる。マネすんな。」と書いておくだけでもずいぶん違うのではないでしょうか。

     

    余裕があれば、double の配列を使うという手もありますね。
    サイズ計算が面倒ですが、テンプレート化してしまえば「お気楽静的バッファ」として便利に使えます。

     

    あと、double のアラインじゃ満足できない(例えば SIMD 用バッファを確保したいような)場合は、
    実行時に演算が入ってしまいますが、以下のようなラッパークラスをかましてやればうまくいくと思います。

     

    Code Snippet

    template <std::size_t BUFFER_SIZE, std::size_t ALIGN_SIZE = sizeof(double)>
    class AlignedBuffer {
        char buf_[BUFFER_SIZE + ALIGN_SIZE - 1];
    public:
        void* GetPtr() {
            const std::size_t offset = (ALIGN_SIZE - (reinterpret_cast<std::size_t>(buf_) % ALIGN_SIZE)) % ALIGN_SIZE;
            return buf_ + offset;
        }
    };

     

    これを、こんな風に使えば、任意のアラインメントを実現できるはずです。

     

    使用例

    AlignedBuffer<sizeof(Hoge)> buf1;
    Hoge* pHoge = static_cast<Hoge*>(buf1.GetPtr());

     

    AlignedBuffer<sizeof(double) * 10, 16 /* must be aligned 16 byte boundaries */> buf2;
    double* pArray = static_cast<double*>(buf2.GetPtr());

     

    ただし、クラスの場合は placement new で初期化する必要がありますので、このやり方は POD での使用にとどめておいた方が無難です。

    2008年6月5日 4:10