Calculating length of variable argument list

Ah hello.

Please consider the following code, part of a custom CMyLog class which basically allows output to be written to a file like this:

CMyLog log;
log.Printf ( _T("This is a string with an %s"), _T("argument") );

BOOL CMyLog::Printf( LPCTSTR lpszFormatString, ... )
{
      
      TCHAR szBuffer[ 1024 ];            
      int nCharsWritten = 0;            

      if ( m_fp )
      {            
            // Deal with varying arguments
             va_list args;            // Our variable argument list
              va_start ( args, lpszFormatString );
              _vsntprintf_s ( szBuffer, 1024, lpszFormatString, args );
            nCharsWritten = _ftprintf( m_fp, szBuffer );
      }
      fflush( m_fp );      // Output data immediately

      ASSERT ( nCharsWritten >= 0 );
      return nCharsWritten >= 0;      // -ve value returned on failure
}

The problem is the current implementation allocated 1024 * ( sizeof TCHAR ) bytes every time I call it.  This may be enough, it may not be.  I am more concerned about the latter.  If I pass more data than space there is in my buffer, the code falls over.

Now, obviously I could get the length of the string lpszFormatString with _tcslen and allocate based on that, but how on earth can I get the length of the varying argument list 'args' ?  Is this possible?

TIA
LVL 19
mrwad99Asked:
Who is Participating?
 
PaulCaswellCommented:
I've never tried this but couldnt you vfprintf to a null file? The return value will give you the number of characters.

It wouldn't be portable but something like:

FILE * nul = fopen("NUL", "wb");
int nChars = vfprintf(nul, format, args);
...

might work.

Paul
0
 
mrwad99Author Commented:
Edited question
0
 
jkrCommented:
You can parse the format string and add up the values. To not overcomplicate the issue, I'd look out for strings ('%s' or '%S'), call '_tcslen()' to calculate their length and assume 8 bytes for all other types, which is safe. If you want to be sure to not waste a byte, you can take their actual size into account.
0
Cloud Class® Course: Certified Penetration Testing

This CPTE Certified Penetration Testing Engineer course covers everything you need to know about becoming a Certified Penetration Testing Engineer. Career Path: Professional roles include Ethical Hackers, Security Consultants, System Administrators, and Chief Security Officers.

 
mrwad99Author Commented:
Hmm, ok.

I have been searching for PAQs and also found out about the macro va_arg, which I can use to get each of the variable arguments.  If I call va_arg twice, I will get the first and the second arguments respectively.

So you are saying I should first parse the format string, searching out all occurences of %<some character> and storing them, then assume the value returned by va_arg will be of that type.  EG if the format string is

"Here is a %s and then a %i"

va_args could be

"string", 10

I would get my list of %<characters> as

%s
%i

therefore should assume that the *first* call to va_args will return a char*, which I can get the length of via _tcslen, and that the *second* call to va_args will return an integer, which I should assume will hold (your suggested) 8 bytes?  Correct?
0
 
jkrCommented:
Yes, that's what I had in mind. BTW, when you are already going through the format specifiers, it might be worthwile to consider using a stringstream here to eliminate the need for buffer allocation, i.e. (pseudocode)


stringstream ss;

for ( ...) {

  switch(format_specifier) {

    case format_charptr:

      ss << (char*) va_arg(arglist,char*);

      break;

    case format_int:

      ss << (int) va_arg(arglist,int);

      break;

  // etc.
  }
     
}

nCharsWritten = _ftprintf( m_fp, ss.str().c_str() );
0
 
mrwad99Author Commented:
Grr, this is not going to work.  I cannot guarantee that I will get all the format specifiers correctly.  For example, if I search out all instances of %, then extract that and the next one character after it, (eg to get %s or %i) it does not cater for the case where we have %I64D to output 64 bit integers.  I cannot just say "get everything from the % to the next space" either, as the space after the format specifier is not compulsary, eg

"%iis an int", 10

Is there no other way to achieve this at all?

Thanks again.
0
 
jkrCommented:
Why don't you take a look at the format string parser code in the CRT sources (they ship with VC++) and create a modified version of 'vstprintf()' that only calculates the space?
0
 
mrwad99Author Commented:
OK, I have a full install of VS 2005 Pro; where would I find the CRT source please?
0
 
jkrCommented:
They reside in "Microsoft Visual Studio 8\VC\crt\src"
0
 
mrwad99Author Commented:
Hmm; thanks for that, but I don't know which file the actual code is in; could you please elaborate (I have no idea what I am looking for)...
0
 
jkrCommented:
Line 994 in 'output.c' - be sure to sit down before opening the file ;o)
0
 
itsmeandnobodyelseCommented:
>>>> BOOL CMyLog::Printf( LPCTSTR lpszFormatString, ... )
instead of that I used to have something like

   bool logError(int errcode, const string& errloc, const string& errinfo);

which logs the error and returns false so that it could used like

   if (something_went_wrong())
        return logError(123, "here in that function", "something_went_wrong");

To make it have arbitrary strings for the 3. argument errinfo, I defined the follwong macro:

#define LOG_ERROR(cod, loc, strm) \
     logError(cod, loc, ((ostringstream&)(ostringstream()<< strm)).str());  

The last term ((ostringstream&)(ostringstream()<< strm)).str() allows to log errors like

   int i = 5;
   double j = 12.3456789;
   char* s = "something";
 
   if (something_went_wrong(i, d, s))
        return LOG_ERROR(123, "here in that function",
            setw(3) << right << i << setw(10) <<  fixed << setprecision(2) << " : " << left << s);

what should allow to get most (though not all) features of printf with an absolute safe C++ mechanism.

Regards, Alex


0
 
itsmeandnobodyelseCommented:
FYI:

((ostringstream&)(ostringstream()<< strm)).str()

does the following:

The 3. argument passed to the LOG_ERROR  macro is 'strm'. It was 'streamed' to an empty ostringstream() temporary:  

      ostringstream()<< strm

The return of that streaming operation was a 'ostream&' cause ostringstream was derived from ostream and so shares all operator<< functions. As we want to get the 'string' value of the streaming operation, we need to 'cast' the result from ostream& to ostringstream& what is safe as the temporary was a ostringstream and then can call the ostringstream::str() member function wich returns the resulting string.

Hope, it was clear.
0
 
mrwad99Author Commented:
Alex

That is a nice bit of code.  Very clever.  However, the problem I see with it is that it cannot be used in the way I need, i.e. I need to be able to output a formatted message, such as

("This is an integer: %i and this is a string: %s"), 10, _T("String")

resulting in

This is an integer: 10 and this is a string: String

With the code you have posted, although I could still output the values, it would have to be more like this:

First we have an integer, and then a string: 10, String.

I cannot insert the integer and the string value at the position in the string I require.

Is this correct or have I missed a fundamental point?

Thanks.
0
 
mrwad99Author Commented:
jkr: thanks for the pointer to the source; I never knew there was a limit of 100 arguments...
0
 
itsmeandnobodyelseCommented:
>>>> Is this correct or have I missed a fundamental point?

LOG_ERROR(123, "",
  "This is an integer:  " << 10 << " and this is a string: " << "String");

would exactly do that output. You could use variables as well:

   int i = 10;
   string s = "String";

LOG_ERROR(123, "",
  "This is an integer:  " << i << " and this is a string: " << s);

and any formatter like setw or setprecision. Furthermore, if you provided an operator<< for your own class objects you could use it like

  Person pers("John", "Lennon", 12, "Penny Lane", "London", 123456);
  LOG_ERROR(123, "", "Address: " << pers);


 

   
0
 
mrwad99Author Commented:
Yeah, that did work Paul!  Nice one!

Overall many thanks to you three for helping me on this, as always it is greatly appreciated.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.