3
votes

Wait! - this is not as silly a question as it sounds. The title is simply succinct.

I have some debugging code to verify the correctness of a data structure and some assertions checking this correctness, which I only want enabled in the Debug build:

{$ifdef DEBUG}
  function Sorted : Boolean;
  function LinearSearchByValue(const T : TType) : NativeInt;
{$endif}

and later in a method:

assert(Sorted);

for example.

In my debug build with assertions enabled, all is fine. However, in my Release build with assertions disabled, the line assert(Sorted); causes a compiler error E2003 Undeclared identifier: 'Sorted'. It's quite right, the identifier isn't declared, but assertions are also turned off and should not be being evaluated or have any code generated. (Trying to trick assert by declaring the method but having no implementation causes the normal error 'Unsatisfied forward or external declaration'. It is clearly looking for the method body as well.)

This is leading to some messy code where methods that should not exist in a Release build at all have to be declared and have a body, in order to compile asserts, which are turned off.

How do I declare methods that exist only in a debug build, and use those in assertions which should also only exist in the debug build, in Delphi 2010?

Further info:

  • I tried wrapping the method declarations with {$ifopt C+}, which checks if assertions are switched on. The calls to assert still failed with 'undeclared identifier'.

  • Compiler options are definitely that assertions are turned off. (I checked :))

  • I have tried wrapping calls to assert that use these methods with {$ifdef DEBUG}. However, this is messy and should not be required. At one point it made me worried that assertions are being compiled even in Release builds, and for performance reasons I don't want them at all. (This is not happening - assert code is not generated.)

  • My current strategy is to declare these methods all the time, but in a Release build ifdef the method body out and fill it with a dummy Result. The aim here is to all assertions to compile, but have as little overhead for the methods as possible, and their return value (should they have turned out to be actually called in a release build) to be clearly wrong.

  • Is there any equivalent to C/C++-style macros in Delphi, where an ASSERT(x) macro would simply be defined as nothing in a release build, causing the compiler to neither see nor care about the statement inside the assertion? This is one of the few clean ways (IMO) to use macros in C++.

So while asserts are not being generated, they are compiled. This loops back to my question: How do I best mix debug-only methods and assertions, and release builds?

2
I think you've already enumerated your options. You just need to decide which one to use. - David Heffernan
I wouldn't use dummy result, I'd raise EAssertionFailed since you should never get there. In fact I have a utility function named RaiseAssertionFailed to do just that. - David Heffernan
@Rob Kennedy, you are incorrect. Assert compiler magic has unique properties not available otherwise and is right tool for checking always-true conditions. - OnTheFly
My previous comment is correct, @User. Mad Except, JclDebug, and Eureka Log all allow a program to discover file names, line numbers, and lots of additional information from an exception, and none requires the use of Assert to get it. - Rob Kennedy
You won't raise exception at runtime, because you won't ever call that code. My code is littered with such things. For example the else clause of a case statement for an enumerated type. I always put RaiseAssertionFailed in there. And of course it never executes. Apart from when I make a mistake. And then I find out about it. Which is what I want. - David Heffernan

2 Answers

4
votes

Don't exclude the code from your release build. Keep the code there, and have it compiled unconditionally.

Your argument against having the code exist in a release build is that it's "messy." But you've already written the code, so it's going to be messy whether it's compiled or not. You may as well let the compiler compile it; it doesn't take appreciably longer to compile the extra code, after all.

Trying to exclude the assertion-related code only makes your code messier by requiring conditional-compilation directives.

Assertions and debug information are orthogonal settings. Assertions can be enabled when debugging is not, and vice versa.

An alternative is to move your assertion-related code into your unit tests. Then they're automatically excluded from all versions of your application, but they're still available for testing.

3
votes

Assertions are usually "elided" or not included in the output executable code at the link step in the compile process. The source code symbols and expressions passed into the assert function must be defined in the compile step so the the compiler can parse and generate code for the assertion and its expression parameter.

Whether the assert is included in the output exe is determined by the linker when copying code to the output file. Depending on the compiler, the expression passed to the assert might still be included in the executable code even if the call to the assert function isn't included.

As noted in other answers and many comments, asserts are not exclusive to debugging. Asserts are also valuable in release code, to verify that must-never-happen conditions don't.

One solution to allow you to leave assertions in your release code while making the functions used in the assertion expression exist only in debug builds is to define do-nothing stubs of the debug functions for the release build. Like this:

// Declaration:
  private function Sorted: Boolean;

// Implementation

{$ifdef DEBUG}
  function Sorted : Boolean;
  begin
// work work work
  end;
{$else}
  function Sorted: Boolean;
  begin
  end;
{$endif}


// used in an assertion:

  assert(Sorted);

This allows you to use the assertions in debug code and in release code without polluting your source code with ifdef wrappers around the assertions, and guarantees that the debug implementations do not exist in your release code.