Recently I've ported a huge chunk of code form C++ to Delphi. The code is full with pointers. Despite project compiling and kind of working, I'm seeing some weird results from time to time, which leads me to think there might be bugs with pointer handling - I've already found a few. The problem is that they are really hard to track down.

Recently I've ported a huge chunk of code form C++ to Delphi. The code is full with pointers. Despite project compiling and kind of working, I'm seeing some weird results from time to time, which leads me to think there might be bugs with pointer handling - I've already found a few. The problem is that they are really hard to track down.

Maybe you have some tips, for such cases:

  var a: PSingle;
  GetMem(a, SizeOf(Single));
  a^ := 1.1;
  someFunc(@a);// <<-- Bug here. someFunc needs PSingle, but get a PPSingle instead

In the example, there's erroneous @ used. Worst of all program does not crash, just deals with that erroneous data and keeps running.

How do you track down pointer bugs like these?

Comments

  1. David Millington Thanks for your ideas! My intent is to keep the code as much alike original as possible, so that any changes to the original code could be easily spotted and appended to the Delphi port.

    At first I have tried doing it with arrays and vars and stuff, but it gets too complicated very fast, since there's pointers and array pointers and pointer pointers and dereferencing and stuff. I figure the less changes I do, the less bugs there will be, and honestly, that seems to be working quite well so far, except for those nasty pointer bugs (hopefully dealt with) and a couple of memleaks (which can be nailed with FastMM anytime).

    I'm also a bit hesitant to push more Delphi artillery into action because of overhead that it might cause. The code should be very CPU efficient by intent.

    There are few more weird bugs left, whose origins are yet mysterious to me, something fishy goes on when agents collide .. Anyway though, it seems to be the Pareto distribution in action - when last 20% of work take 80% of time :-)

    The code in question is RecastNavigationDelphi, which you can check out at GitHub (comments are welcome!).

    ReplyDelete
  2. Krom Stern "I figure the less changes I do, the less bugs there will be, and honestly, that seems to be working quite well so far, except for those nasty pointer bugs."

    The pointers are the source of your bugs, particularly when passing them to parameters.  This is because C (and therefore C++ as well) has very bad pointer semantics.  Getting rid of them by transforming them into var parameters and arrays not only makes your code less buggy, (not more,) it also makes it more understandable, since the code explicitly states up-front whether it's expecting a single value by reference or an array of values.  In C, there's no way to tell without reading the implementation.

    "I'm also a bit hesitant to push more Delphi artillery into action because of overhead that it might cause. The code should be very CPU efficient by intent."

    Turning a pointer parameter into a var parameter adds zero overhead.  It should compile to exactly the same code; it's just more clear what's going on, and less likely to cause bugs for you.

    Replacing a pointer with a const dynamic array parameter adds zero overhead.  Making it non-const adds a negligible amount of overhead (unless you're in a very tight loop of course.)  Actually using a dynamic array in the first place adds a negligible amount of overhead, but again, it's something you're really not likely to notice, even in code that's built to be very CPU efficient.  The only time I've run into issues with dynamic arrays and efficiency was one time when working with an array of TValue objects, which are compiler-managed in a highly inefficient way.  If you're not doing that (which you probably aren't because C++ doesn't have compiler-managed types in the same sense as Delphi) you're unlikely to run into efficiency issues.

    Bottom line: If you translate C++ into Delphi but you don't use Delphi idioms, all you're doing is "writing C++ in Delphi," and you end up getting the worst of both worlds.

    ReplyDelete
  3. Mason Wheeler Thanks for your opinion.

    I have tried "arrays" approach at first and had to reject it after a week or so. I can not recall exact details, sorry, it was ~3 months ago. From what I recall there was a lot of cases when doing things Delphi way meant bloating the code and breaking comparison similarity with C++ code. Some things are just neater when you can pass a PSingle instead of slicing var from the middle of an array.

    Untyped pointers were indeed a cause of almost a dozen of bugs I've introduced while porting the code. Thankfully now they are fixed as types pointers are used. Problem solved (hopefully).

    Var and const parameters are prone to bugs just as well. Forget to add it once or add it by mistake and there's a couple of hours bughunt of the same nature "why is agent sticking to a wall", but this time there's no compiler hint "pointer type mismatch".

    Delphi is cool to allow both approaches to arrays management and I'm glad I got my hands dirty in pointers, that learned me a thing or two.

    ReplyDelete

Post a Comment