Home » Community » Forums » » ASSERT(), VERIFY() and TRACE() for non-MFC Applications
  Intel sponsors gamedev.net search:   
[Control Panel] [Register] [Bookmarks] [Who's Online] [Active Topics] [Stats] [FAQ] [Search]

Add Forum to Favorites |  Send Topic To a Friend | View Forum FAQ | Track this topic


 Last Thread Next Thread 
 ASSERT(), VERIFY() and TRACE() for non-MFC Applications
Post Reply 
Great and useful tip ! Could you provide us with a few example code snippets ?
That would be great.

- Wolf

 User Rating: 1469   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

hmmm I hate that when the < and > don't show up because of HTML.. solution ?

besides that, thx! will try them when I come home..

cya,
Phil

 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

quote:
Original post by phueppl1
hmmm I hate that when the < and > don't show up because of HTML.. solution ?

besides that, thx! will try them when I come home..

cya,
Phil


The main idea is to have a simple and handy set of debugging macros that can be used w/o MFC.

Create a new empty console project. Do not activate MFC support, no StdAfx.h, nothing. Create a new (empty) file and name it main.cpp. Add it to the project. Also add the file debug.cpp to the projrcts source files. Ensure that debug.h is in the header path (or copy it to the project dir).

Add following code to main.cpp:
#include "debug.h"

int main()
{
	int i=0, j=1, k=2;
	TRACE("Hello debugger world, here some vars %d, %d, %d\n", i, j, k);
	ASSERT(0);
	return 0;
}
 


You will see
Hello debugger world, here some vars 0, 1, 2 
appear in the debugger output window, and the program will stop (as if you had put a brekpoint) on the line
ASSERT(0); 


You can add debug.cpp and debug.h to any kind of non-MFC project (Win32 Console, Win32 GUI app). TRACE() works like the old good printf(), with the difference that it prints to the debuggers output window.

CU,

Gabriel.

Forever trusting who we are
And nothing else matters
- Metallica


 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

quote:
Original post by phueppl1
hmmm I hate that when the < and > don't show up because of HTML.. solution ?

besides that, thx! will try them when I come home..

cya,
Phil

Solution? A mail to Dave. In the meanwhile:
    
//debug.h

#ifndef __DEBUG_H__

#define __DEBUG_H__

#ifdef _DEBUG

void _trace(char *fmt, ...);

#define ASSERT(x) {if(!(x)) _asm{int 0x03}}

#define VERIFY(x) {if(!(x)) _asm{int 0x03}}

#else

#define ASSERT(x)

#define VERIFY(x) x

#endif

#ifdef _DEBUG

#define TRACE _trace

#else

inline void _trace(char* fmt, ...) { }

#define TRACE  1 ? (void)0 : _trace

#endif

#endif // __DEBUG_H__



//debug.cpp:

#ifdef _DEBUG

#include <stdio.h>

#include <stdarg.h>

#include <windows.h>

void _trace(char *fmt, ...)
{
char out[1024];
	va_list body;
	va_start(body, fmt);
	vsprintf(out, fmt, body);
	va_end(body);
	OutputDebugString(out);
}

#endif
  



Forever trusting who we are
And nothing else matters
- Metallica


[edited by - Gabriel Fleseriu on July 24, 2002 10:22:04 AM]

 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

What if your output is more than 1024 chars?
Wouldn't something like

inline void TRACE(char *fmt, ...)
{
	size_t iSize = strlen(fmt);
	char* out = new char[iSize+1];
	va_list body;
	va_start(body, fmt);
	vsprintf(out, fmt, body);
	va_end(body);
	OutputDebugString(out);
	delete[] out;
}
 


be better?

 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Good useful sweet snippet.

One thing I would add is that while perhaps the MFC ASSERT() just brekas into the debugger when an assert fails, it may be more useful to pop up a messagebox asking the user if they wish to break or not. I don't know if the MFC ASSERT() does this or not ( I suspect not from the article ) however it may be useful and easily implemented just using MessageBox() and passing the assertion as text using the # precompiler operator.

just a thought
Toby

Gobsmacked - by Toby Murray

 User Rating: 1010   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

quote:
Original post by Technocrat
What if your output is more than 1024 chars?
Wouldn't something like

inline void TRACE(char *fmt, ...)
{
	size_t iSize = strlen(fmt);
	char* out = new char[iSize+1];
	va_list body;
	va_start(body, fmt);
	vsprintf(out, fmt, body);
	va_end(body);
	OutputDebugString(out);
	delete[] out;
}
   


be better?


not really... the idea might be good, but the execution isn't. what if i had something like this:

TRACE("name is %s", player[0]->name);

it would allocate 10 + 1 chars. those 11 chars are enough to fit "name is %s", but is it enough to fit "name is shurcool"? i hope you see what i'm saying.

---
shurcool
wwdev


[edited by - shurcool on July 24, 2002 11:55:31 AM]

 User Rating: 1137   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

quote:
Original post by Gabriel Fleseriu
[Solution? A mail to Dave. In the meanwhile:



Actually, I'm the one that put this up.. I have no idea where Dave is right now, and we needed a new article. It's my first time editing one in over a year, so cut me some slack. I'm fixing it now.




Kevin "Khawk" Hawkins
CEO and News Director, GameDev.net
Author, OpenGL Game Programming
Developer Diary



 User Rating: 2039   |  Rate This User  Send Private MessageView ProfileView Journal Report this Post to a Moderator | Link

quote:
Original post by shurcool

not really... the idea might be good, but the execution isn't. what if i had something like this:

TRACE("name is %s", player[0]->name);

it would allocate 10 + 1 chars. those 11 chars are enough to fit "name is %s", but is it enough to fit "name is shurcool"? i hope you see what i'm saying.

---
shurcool
wwdev


[edited by - shurcool on July 24, 2002 11:55:31 AM]


Hmm, good point. I guess 1024 is a safe number, but for something that is supposed to help when you have problems, it would seem to me that having a fixed size char VAR seems like an un-needed problem.

Other than that good code.



 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

quote:
Original post by tobymurray
Good useful sweet snippet.

One thing I would add is that while perhaps the MFC ASSERT() just brekas into the debugger when an assert fails, it may be more useful to pop up a messagebox asking the user if they wish to break or not. I don't know if the MFC ASSERT() does this or not ( I suspect not from the article ) however it may be useful and easily implemented just using MessageBox() and passing the assertion as text using the # precompiler operator.

just a thought
Toby

Gobsmacked - by Toby Murray



Yes, you could add as much functionality to ASSERT() as you would feel necessary. See the MFC implementation. I avoided MessageBox() and other fancy features because they don't really add value to the snippet, IMHO. Besides of thet: once the execution is interrupted - where is the difference between pressing F5 in VC++ or selecting "Yes" in the message box?


 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Inspired by this article i've written a more C++ approach to TRACE() some of you might find useful:

    

/////////////////////////////////////

// IN TRACE.H


#ifndef TRACE_H
#define TRACE_H

#ifdef _DEBUG

#include <iostream>

class Tracebuf : public std::streambuf
{
public:
	int sync();
	int overflow(int ch);

private:
	void WriteToDebug(const char* pc, std::streamsize s);
};

extern std::ostream tout;

#define TRACE(x)	tout << x

#else // _DEBUG


#define TRACE(x)

#endif // _DEBUG


#endif // TRACE_H


  
  



  

/////////////////////////////////////

// IN TRACE.CPP



#include "Trace.h"

#ifdef _DEBUG

static Tracebuf tbuf;
std::ostream tout(&tbuf);


int Tracebuf::sync()
{
	std::streamsize n = pptr() - pbase();
	if (n)
		WriteToDebug(pbase(), n);
	return n;
}


int Tracebuf::overflow(int ch)
{
	std::streamsize n = pptr() - pbase();
	if (n && sync())
		return EOF;
	if (ch != EOF)
	{
		char c(ch);
		WriteToDebug(&c, 1);
	}
	pbump(-n);
	return 0;
}

// pass as a char* as i believe the stream is not null terminated

void Tracebuf::WriteToDebug(const char* pc, std::streamsize s)
{
	std::string str(pc, s);
	::OutputDebugString(str.c_str());
}

#endif // _DEBUG


  
  


You can use any stream manipulators on it, like endl, and it will automatically format numbers for you - just like cout:

  

TRACE("Look at me, im debugging! : " << 111222333 << endl);
TRACE("And again, with that in hex: " << hex << 111222333 << endl);

    



Please bear in mind i've only just written it if you are gonna use it, so its minimally tested.


[edit] - remember to use << endl or << flush at the end of any TRACE() calls, so the buffer'll flush immediately. If you're afraid you'll forget, just modify

#define TRACE(x) tout << x

to

#define TRACE(x) tout << x << flush


[edited by - sark on July 27, 2002 2:03:52 AM]

 User Rating: 1003   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

You can use the preprocessor directive NDEBUG to detect a release build portably (and the lack thereof signifies a debug build)

#ifndef NDEBUG
//Debug code
#else
//Release code
#endif
 


If you are concerned about string buffer overruns (I am) you can use std::stringstream instead of printf (I do). Or at the least use the n flavor of sprintf (nsprintf? snprintf?) so it doesn't corrupt the stack.

 User Rating: 1726   |  Rate This User  Send Private MessageView ProfileView Journal Report this Post to a Moderator | Link

Hey man.That was so cool to know but i have a question of course.

This is part of my class function:

bool COGLWindow::Create(char *strWindowName,int width,int height,int bpp)
{
if(!strWindowName || (*strWindowName) == '\0')
{
g_log.Add("Invalid window name");
return false;
}

return true;
}

So my question is would it be better if I use:

ASSERT(strWindowName == NULL);
ASSERT((*strWindowName) == '\0');

10x;



The pain is coming...this sommer!!!

 User Rating: 1000   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

quote:
Original post by Mihail121
ASSERT(strWindowName == NULL);


That would check whether the POINTER is 0
quote:

ASSERT((*strWindowName) == '\0');


This assumes a valid pointer and checks for empty string.

First assert on a valid (i.e. non-null) pointer. Then dereference it and see if the string is not empty.

Of course, the fact that a pointer is not 0 is no guarantee that the pointer is valid. It is good habit to initialize pointers to 0 and to set them 0 again after deletion.
char *p = 0;
//...
p = new char[100];
//... use p
delete[] p;
p = 0;
//...
  






Forever trusting who we are
And nothing else matters
- Metallica


[edited by - Gabriel Fleseriu on August 1, 2002 4:59:11 AM]

 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

quote:
Original post by Technocrat
[quote]Original post by shurcool

not really... the idea might be good, but the execution isn't. what if i had something like this:

TRACE("name is %s", player[0]->name);

it would allocate 10 + 1 chars. those 11 chars are enough to fit "name is %s", but is it enough to fit "name is shurcool"? i hope you see what i'm saying.

---<br><SPAN CLASS=editedby>shurcool<br><a href="http://www22.brinkster.com/warwormsdev/">wwdev</a></SPAN>

<SPAN CLASS=editedby>[edited by - shurcool on July 24, 2002 11:55:31 AM]</SPAN>


Hmm, good point. I guess 1024 is a safe number, but for something that is supposed to help when you have problems, it would seem to me that having a fixed size char VAR seems like an un-needed problem.

Other than that good code.



The way to get around this in windows is with _vnsprintf();

inline void TRACE(const char *str, ...)
{
	va_list marker;
	int count = -1;
	string message;
	
	va_start(marker, str);
	while(count == -1) {
		char buf[1024];

		// _vsnprintf returns -1 when it doesn't finish writing, or number of chars in buf
		count = _vsnprintf(buf, 1023, str, marker);
		OutputDebugString(buf);
	}

	return message;
}

 


 User Rating: 1015    Report this Post to a Moderator | Link

I think you did not mean to put string message; and return message;

[edited by - Technocrat on August 5, 2002 12:57:00 PM]

 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

For moderator or GDStaff:

I submitted an article for posting on GDNET. I understand the staff is wrapped up in the judging of the 4E3 contest, but I submitted before then, and then during. I guess I will have to submit every week until I get a response, is that a prudent course of action?

The article has been editted by 2 different people and there isn't any extensive coverage of the subject matter in the Articles and Resources section. The article is relavent to GDNET and can answer questions that have been repeatedly asked in the forums.

Does it take this long to get a response?

MKH:
It can take some time to get a response for the acceptance of an article, as they like to read them over prior to giving it a thumbs up or down. If Dave decides to run it, it can take several months before it's actually posted. They like to keep a stockpile of articles available as a buffer, so they don't run out during periods of low or no submissions.

[edited by - Magmai Kai Holmlor on August 21, 2002 11:46:41 PM]

 User Rating: 1015    Report this Post to a Moderator | Link

I took a look at MFC CString::Format and this should work for any length of string messages.
  
// Debug.h

#include <windows.h>

#ifndef __DEBUG_H__

#define __DEBUG_H__

#ifdef _DEBUG

void _trace(LPCTSTR *fmt, ...);
#define ASSERT(x) {if(!(x)) _asm{int 0x03}}

#define VERIFY(x) {if(!(x)) _asm{int 0x03}}

#else

#define ASSERT(x)

#define VERIFY(x) x

#endif

#ifdef _DEBUG

#define TRACE _trace

#else

inline void _trace(LPCTSTR* fmt, ...) { }
#define TRACE 1 ? (void) 0 : _trace

#endif

#endif // __DEBUG_H__

  

  
// Debug.cpp

#include <Debug.h>

#include <tchar.h>

#include <malloc.h>

#define TCHAR_ARG   TCHAR

#define WCHAR_ARG   WCHAR

#define CHAR_ARG    char


struct _AFX_DOUBLE  { BYTE doubleBits[sizeof(double)]; };

#ifdef _X86_

#define DOUBLE_ARG  _AFX_DOUBLE

#else

#define DOUBLE_ARG  double

#endif


#define FORCE_ANSI      0x10000

#define FORCE_UNICODE   0x20000

#define FORCE_INT64     0x40000


// formatting (using wsprintf style formatting)

void _trace(LPCTSTR lpszFormat, ...)
{
   va_list argList;
   va_start(argList, lpszFormat);

   va_list argListSave = argList;

   // make a guess at the maximum length of the resulting string

   int nMaxLen = 0;
   for (LPCTSTR lpsz = lpszFormat; *lpsz != '\0'; lpsz = _tcsinc(lpsz))
   {
      // handle '%' character, but watch out for '%%'

      if (*lpsz != '%' || *(lpsz = _tcsinc(lpsz)) == '%')
      {
         nMaxLen += _tclen(lpsz);
         continue;
      }

      int nItemLen = 0;

      // handle '%' character with format

      int nWidth = 0;
      for (; *lpsz != '\0'; lpsz = _tcsinc(lpsz))
      {
         // check for valid flags

         if (*lpsz == '#')
            nMaxLen += 2;   // for '0x'

         else if (*lpsz == '*')
            nWidth = va_arg(argList, int);
         else if (*lpsz == '-' || *lpsz == '+' || *lpsz == '0' ||
            *lpsz == ' ')
            ;
         else // hit non-flag character

            break;
      }
      // get width and skip it

      if (nWidth == 0)
      {
         // width indicated by

         nWidth = _ttoi(lpsz);
         for (; *lpsz != '\0' && _istdigit(*lpsz); lpsz = _tcsinc(lpsz))
            ;
      }
      ASSERT(nWidth >= 0);

      int nPrecision = 0;
      if (*lpsz == '.')
      {
         // skip past '.' separator (width.precision)

         lpsz = _tcsinc(lpsz);

         // get precision and skip it

         if (*lpsz == '*')
         {
            nPrecision = va_arg(argList, int);
            lpsz = _tcsinc(lpsz);
         }
         else
         {
            nPrecision = _ttoi(lpsz);
            for (; *lpsz != '\0' && _istdigit(*lpsz); lpsz = _tcsinc(lpsz))
               ;
         }
         ASSERT(nPrecision >= 0);
      }

      // should be on type modifier or specifier

      int nModifier = 0;
      if (_tcsncmp(lpsz, _T("I64"), 3) == 0)
      {
         lpsz += 3;
         nModifier = FORCE_INT64;
#if !defined(_X86_) && !defined(_ALPHA_)

         // __int64 is only available on X86 and ALPHA platforms

         ASSERT(FALSE);
#endif

      }
      else
      {
         switch (*lpsz)
         {
            // modifiers that affect size

         case 'h':
            nModifier = FORCE_ANSI;
            lpsz = _tcsinc(lpsz);
            break;
         case 'l':
            nModifier = FORCE_UNICODE;
            lpsz = _tcsinc(lpsz);
            break;

            // modifiers that do not affect size

         case 'F':
         case 'N':
         case 'L':
            lpsz = _tcsinc(lpsz);
            break;
         }
      }

      // now should be on specifier

      switch (*lpsz | nModifier)
      {
         // single characters

      case 'c':
      case 'C':
         nItemLen = 2;
         va_arg(argList, TCHAR_ARG);
         break;
      case 'c'|FORCE_ANSI:
      case 'C'|FORCE_ANSI:
         nItemLen = 2;
         va_arg(argList, CHAR_ARG);
         break;
      case 'c'|FORCE_UNICODE:
      case 'C'|FORCE_UNICODE:
         nItemLen = 2;
         va_arg(argList, WCHAR_ARG);
         break;

         // strings

      case 's':
         {
            LPCTSTR pstrNextArg = va_arg(argList, LPCTSTR);
            if (pstrNextArg == NULL)
               nItemLen = 6;  // "(null)"

            else
            {
               nItemLen = lstrlen(pstrNextArg);
               nItemLen = max(1, nItemLen);
            }
         }
         break;

      case 'S':
         {
#ifndef _UNICODE

            LPWSTR pstrNextArg = va_arg(argList, LPWSTR);
            if (pstrNextArg == NULL)
               nItemLen = 6;  // "(null)"

            else
            {
               nItemLen = wcslen(pstrNextArg);
               nItemLen = max(1, nItemLen);
            }
#else

            LPCSTR pstrNextArg = va_arg(argList, LPCSTR);
            if (pstrNextArg == NULL)
               nItemLen = 6; // "(null)"

            else
            {
               nItemLen = lstrlenA(pstrNextArg);
               nItemLen = max(1, nItemLen);
            }
#endif

         }
         break;

      case 's'|FORCE_ANSI:
      case 'S'|FORCE_ANSI:
         {
            LPCSTR pstrNextArg = va_arg(argList, LPCSTR);
            if (pstrNextArg == NULL)
               nItemLen = 6; // "(null)"

            else
            {
               nItemLen = lstrlenA(pstrNextArg);
               nItemLen = max(1, nItemLen);
            }
         }
         break;

      case 's'|FORCE_UNICODE:
      case 'S'|FORCE_UNICODE:
         {
            LPWSTR pstrNextArg = va_arg(argList, LPWSTR);
            if (pstrNextArg == NULL)
               nItemLen = 6; // "(null)"

            else
            {
               nItemLen = wcslen(pstrNextArg);
               nItemLen = max(1, nItemLen);
            }
         }
         break;
      }

      // adjust nItemLen for strings

      if (nItemLen != 0)
      {
         if (nPrecision != 0)
            nItemLen = min(nItemLen, nPrecision);
         nItemLen = max(nItemLen, nWidth);
      }
      else
      {
         switch (*lpsz)
         {
            // integers

         case 'd':
         case 'i':
         case 'u':
         case 'x':
         case 'X':
         case 'o':
            if (nModifier & FORCE_INT64)
               va_arg(argList, __int64);
            else
               va_arg(argList, int);
            nItemLen = 32;
            nItemLen = max(nItemLen, nWidth+nPrecision);
            break;

         case 'e':
         case 'g':
         case 'G':
            va_arg(argList, DOUBLE_ARG);
            nItemLen = 128;
            nItemLen = max(nItemLen, nWidth+nPrecision);
            break;

         case 'f':
            {
               double f;
               LPTSTR pszTemp;

               // 312 == strlen("-1+(309 zeroes).")

               // 309 zeroes == max precision of a double

               // 6 == adjustment in case precision is not specified,

               //   which means that the precision defaults to 6

               pszTemp = (LPTSTR)_alloca(max(nWidth, 312+nPrecision+6));

               f = va_arg(argList, double);
               _stprintf( pszTemp, _T( "%*.*f" ), nWidth, nPrecision+6, f );
               nItemLen = _tcslen(pszTemp);
            }
            break;

         case 'p':
            va_arg(argList, void*);
            nItemLen = 32;
            nItemLen = max(nItemLen, nWidth+nPrecision);
            break;

            // no output

         case 'n':
            va_arg(argList, int*);
            break;

         default:
            ASSERT(FALSE);  // unknown formatting option

         }
      }

      // adjust nMaxLen for output nItemLen

      nMaxLen += nItemLen;
   }

   TCHAR*   szData = new TCHAR[nMaxLen + 1];
   VERIFY(_vstprintf(szData, lpszFormat, argListSave) <= nMaxLen);
   OutputDebugString(szData);
   delete [] szData;

   va_end(argListSave);
   va_end(argList);
}
  

EDIT1: Fixed source includes.

[edited by - dalleboy on August 5, 2002 1:58:28 PM]

 User Rating: 1149   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Did anyone read my post above? My solution avoids the 1024 buffer problem, automatically formats numbers and floats etc and you can use the standard iostream manipulators.

 User Rating: 1003   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

quote:
Original post by sark
Did anyone read my post above? My solution avoids the 1024 buffer problem, automatically formats numbers and floats etc and you can use the standard iostream manipulators.
Sure we did, but iostream-like output sux, printf-like output is much easier (especially for us c-programmers).

Does anyone <b>really</b> use iostream for output of text in games?

 User Rating: 1015    Report this Post to a Moderator | Link

There may be a little problem with the TRACE macro (I'm not sure...). I know the code is compiled out, but what about the parameters passed to the macro? Are they compiled, taking place in the data segment of the final executable?

A good test would be to use a function as a parameter, and to see if it's called in release mode...

 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Can somebody tell me what you can do about the assembler code when you use GCC for compiling? Is there a possibility to get this working?

 User Rating: 1015    Report this Post to a Moderator | Link

rather than using non-portable assembler to implement ASSERT, you could just use the standard assert() macro. This is garanteed to work everywhere, and is portable.

 User Rating: 1015    Report this Post to a Moderator | Link

All times are ET (US)

Post Reply
 Last Thread Next Thread 
Forum Rules:
You may not post new threads
You may post replies
You may not edit your posts
You may not use HTML in your posts
Jump To:
Administrative Options: