5

この機能はDelphi7アプリケーションにあり、FastMM4v4.99をプロジェクトに含めるまでは非常にうまく機能していました。含まれると、FastMM4は次のエラーメッセージを生成しました:「FastMMはFreeMem操作中にエラーを検出しました。ブロックフッターが破損しています。」FreeMem行で実行が停止します。

function BinaryFieldToArrayOfWord( aBinaryField   : TVarBytesField;
                                   out aArrValues : TArrWord ) : Boolean;
var
  p : Pointer;
begin
  if not aBinaryField.IsBlob then
  begin
    GetMem( p, aBinaryField.DataSize );     
    try
      if aBinaryField.GetData( p ) then         
      begin
        // do something
      end;
    finally
      FreeMem( p, aBinaryField.DataSize );
    end;
  end; // if
end;

最初は、関数にバグがあるはずだと思いましたが、Delphi7ヘルプのこのTField.GetDataメソッドの例と実質的に同じです。

{ Retrieve the "raw" data from Field1 }
with Field1 do
begin
  if not IsBlob  { this does not work for BLOB fields }
  begin
    { Allocate space }
    GetMem(MyBuffer, DataSize);
    try
      if not GetData(MyBuffer) then
         MessageDlg(DisplayName + ' is NULL', mtInformation, [mbOK], 0)
      else 
         { Do something with the data };
    finally
      { Free the space }
      FreeMem(MyBuffer, DataSize);
    end;
  end;
end;

インターネット上で、上記のエラーメッセージは、データ用の十分なスペースがないことが原因であることがよくあります。そのため、メモリブロックのサイズを大きくすると、エラーメッセージが消え、関数は期待どおりに機能しました。いくつかの実験の後、2バイトが必要で十分であることがわかりました。

    GetMem( p, aBinaryField.DataSize + 2 );

    FreeMem( p, aBinaryField.DataSize + 2 );

それは私の問題を解決しましたが、私はその背景がわからないので、私はこの解決策で完全にリラックスしていません。このメッセージの理由を知っておくと便利です。FastMM4はそれ自身のフッターのために追加の2バイトを必要としますか?

更新:デバッグに1日を費やした後、Delphi7TField.GetDataメソッドにバグがあると今では信じています。割り当てられたメモリブロックを超えて2つのゼロバイトを書き込みます。FastMM4を使用して、または使用せずに試してみましたが、どちらの場合も上書きされます(したがって、FastMM4エラーではありません)。また、フィールドをTVarBytesFieldとして型キャストしてみましたが、違いはありません。これが私が詳細なコメントとともに使用したコードで、結果が含まれています。私の唯一の残りの質問:彼らは後のDelphisでこのバグを修正しましたか?

  procedure TfrmMain_PBC_TH.btnDEBUGClick(Sender: TObject);
  type
    TArrBytes = array of Byte;  
  var
    p       : Pointer;
    qryTest : TADOQuery;
  begin
    qryTest := TADOQuery.Create( Application );
    try
      // The type of the TQM_BinaryData.BinData column in the MSSQL database is a       varbinary(7900). 
      // Load the #168 binary data record. It contains exactly 7900 bytes, and the value of each byte is 255
      qryTest.Connection := MainConn;
      qryTest.SQL.Add('SELECT [BinData] FROM [TQM_BinaryData] WHERE [Id] = 168');
      qryTest.Open;

      // Allocate the memory block for this.
      GetMem( p, qryTest.FieldByName('BinData').DataSize );
      // DataSize is 7902 because all TVarBytesFields have 2 byte prefix in Delphi, containing the data length.
      // So the size of the allocated memory block is 7902 bytes - we are correct so far.
      try
        // Values of the first four bytes beyond the end of the memory block (memory thrash at this point) before GetData:
        // TArrBytes(p)[7902] = 96
        // TArrBytes(p)[7903] = 197
        // TArrBytes(p)[7904] = 219
        // TArrBytes(p)[7905] = 43

        // Critical point: get the data from the field with the Delphi GetData method
        qryTest.FieldByName('BinData').GetData( p );

        // Values after GetData:
        // TArrBytes(p)[0]    = 220    TArrBytes(p)[0] and TArrBytes(p)[1] contains the length of the binary data
        // TArrBytes(p)[1]    = 30     it is correct as 30 * 256 + 220 = 7900
        // TArrBytes(p)[2]    = 255    actual data starts
        // TArrBytes(p3[2]    = 255    
        // ...
        // TArrBytes(p)[7900] = 255
        // TArrBytes(p)[7901] = 255    actual data ends
        // TArrBytes(p)[7902] = 0      changed from 96!
        // TArrBytes(p)[7903] = 0      changed from 197!
        // TArrBytes(p)[7904] = 219    no change
        // TArrBytes(p)[7905] = 43     no change
      finally
        // Here FastMM4 throws the block footer corrupt error because GetData modified the 2 bytes after the allocated memory block 
        FreeMem( p );
      end;

      qryTest.Close;
    finally
      qryTest.Free;
    end;
  end;

データブレークポイントを追加した後のコールスタックは次のとおりです。

    @FillChar(???,???,???)
    TDataSet.DataConvert($7D599770,$12F448,$7D51F7F0,True)
    VarToBuffer
    TCustomADODataSet.GetFieldData($7D599770,$7D51F7F0,True)
    TField.GetData($7D51F7F0,True)
    TfrmMain_PBC_TH.btnDEBUGClick($7FF7A380)
    TControl.Click
    TButton.Click
4

1 に答える 1

5

FastMMは、割り当てたブロックの最後にメモリを割り当て、既知の値をそこに書き込みます。次に、割り当てを解除すると、FastMMはそれらの値が期待どおりであることを確認します。そうでない場合は、コードがメモリブロックの終わりを超えて書き込んでいることをFastMMが認識しているため、表示されるエラーが発生します。

したがって、try / finalブロック内の何かが、メモリブロックの終わりを超えて書き込みを行っています。そのため、サイズを大きくするとFastMMエラーが削除されます。そのコードを見ないと、何が間違っているのか正確に判断できず、問題を解決するためにデバッグを行う必要があります。

あなたは自分の「解決策」に関心を持つのはまったく正しいことです。試行錯誤は決して合理的なプログラミング方法ではありません。プログラムがブロックの終わりを超えて書き込んでいる理由を見つける必要があります。

これを行う1つの方法は、このブロックの終わりのすぐ先のアドレスにデータブレークポイントを設定することです。これにより、デバッガーはブロックの終わりを超えて書き込むコードを強制的に中断します。

余談ですが、2番目のパラメーターをFreeMemに渡す必要はありません。そうすることで、コードの保守が難しくなり、まったく目的がなくなります。FreeMemへのポインタだけを渡します。

于 2012-04-26T09:09:37.560 に答える