Printf / Variable length arguement Function Implementation

Started by
7 comments, last by Zahlman 17 years, 6 months ago
I am currently attempting to produce a method which I can use like printf within my program but also includes a priority of the log and a source (so I can choose my logging level easily). However my current implementation is going iffy, it can find the first arguement (which has its address cast), however the next ones are completely lost and junk is printed. Code is adapted from the original at http://www.menie.org/georges/embedded/index.html Report is the top level function, it converts the std::strings to char* and calls the correct functions. This module also provides the "priority" check.

int Report (std::string format, int priority, std::string source, ...)
{
    char* cp_format = const_cast<char*>(format.c_str());
    char* cp_source = const_cast<char*>(source.c_str());
    register int *varg = (int*) (&cp_format);    // source is a better target imo

    if (priority <= CURRENT_PRIORITY_LEVEL)
        return print (0,varg);
    return -1;
}

int print (char** out, int* varg)
{
    register int width, pad;
    register int pc = 0;
    register char* format = (char*)(*varg++);
    char scr[2];
    // Move past first 2 args to get to list
    varg++;
    varg++;

    // Parse the format component writing out the char's and adding vars.
    for ( ; *format != 0; ++format)
    {
        if (*format == '%')
        {
            ++format;
            width = pad = 0;
            if (*format == '\0')
                break;
            if (*format == '%') goto out;
	    if (*format == '-') 
            {
		++format;
		pad = PAD_RIGHT;
	    }
	    while (*format == '0') 
            {
		++format;
		pad |= PAD_ZERO;
	    }
            for( ; *format >= '0' && *format <= '9'; ++format) 
            {
		width *= 10;
		width += *format - '0';
	    }
	    if( *format == 's' ) 
            {
		register char *s = *((char **)varg++);
		pc += prints (out, s?s:"(null)", width, pad);
		continue;
            }
	    if( *format == 'd' ) 
            {
		pc += printi (out, *varg++, 10, 1, width, pad, 'a');
		continue;
	    }
            if( *format == 'x' ) 
            {
		pc += printi (out, *varg++, 16, 0, width, pad, 'a');
		continue;
	    }
            if( *format == 'X' ) 
            {
		pc += printi (out, *varg++, 16, 0, width, pad, 'A');
		continue;
	    }
	    if( *format == 'u' ) 
            {
		pc += printi (out, *varg++, 10, 0, width, pad, 'a');
		continue;
	    }
	    if( *format == 'c' ) 
            {
		/* char are converted to int then pushed on the stack */
		scr[0] = *varg++;
		scr[1] = '\0';
		pc += prints (out, scr, width, pad);
		continue;
	    }
	}
	else {
	out:
	    printchar (out, *format);
	    ++pc;
	}
    }
    if (out) **out = '\0';
    return pc;
}
Print Integers:

#define PRINT_BUF_LEN 12

static int printi(char **out, int i, int b, int sg, int width, int pad, int letbase)
{
	char print_buf[PRINT_BUF_LEN];
	register char *s;
	register int t, neg = 0, pc = 0;
	register unsigned int u = i;

	if (i == 0) {
		print_buf[0] = '0';
		print_buf[1] = '\0';
		return prints (out, print_buf, width, pad);
	}

	if (sg && b == 10 && i < 0) {
		neg = 1;
		u = -i;
	}

	s = print_buf + PRINT_BUF_LEN-1;
	*s = '\0';

	while (u) {
		t = u % b;
		if( t >= 10 )
			t += letbase - '0' - 10;
		*--s = t + '0';
		u /= b;
	}

	if (neg) {
		if( width && (pad & PAD_ZERO) ) {
			printchar (out, '-');
			++pc;
			--width;
		}
		else {
			*--s = '-';
		}
	}

	return pc + prints (out, s, width, pad);
}
Print Strings

#define PAD_RIGHT 1
#define PAD_ZERO 2

static int prints(char **out, const char *string, int width, int pad)
{
	register int pc = 0, padchar = ' ';

	if (width > 0) {
		register int len = 0;
		register const char *ptr;
		for (ptr = string; *ptr; ++ptr) ++len;
		if (len >= width) width = 0;
		else width -= len;
		if (pad & PAD_ZERO) padchar = '0';
	}
	if (!(pad & PAD_RIGHT)) {
		for ( ; width > 0; --width) {
			printchar (out, padchar);
			++pc;
		}
	}
	for ( ; *string ; ++string) {
		printchar (out, *string);
		++pc;
	}
	for ( ; width > 0; --width) {
		printchar (out, padchar);
		++pc;
	}

	return pc;
}
Putchars

#define putchar(c) outbyte(c)

static void printchar(char **str, int c)
{
	extern int putchar(int c);
	if (str) {
		**str = c;
		++(*str);
	}
	else (void)putchar(c);
}
I think I might be able to use the original source unmodified (doing varg++ twice seems to result in an unknown error under msvc, however not doing this prints garbage) if I use the address of source rather than the formatting string, but then I need to pass the address of the formatting string as well. I am not really sure where the error occurs or why, my problems occur in the Print function, report passes correctly and the lower level ones (print int, print string, putchar) all function as they should as they are unmodified. Any hints or thoughts, I did google this however it turns up a lot of people doing stuff with printf (error reporting) and a lot of people implementing stuff, with a printf statement on the page :(.
Advertisement
i've just quickly ran throught you code and i've got only one question?
what's your point in implementing your own printf? can't you just use v* versions of original printf? (for your case vprintf, vsprintf, vfprintf)
you will just supply argument list as a parameter instead of actual arguments.

m.
I quite agree with mx. If you must use the varadic form, why on earth not use the standard library to assist:

void my_printf(int priority,const char *s,...){    va_list va; va_start(va,s);    if(priority>3)        {        printf("Prefix: ");        vprintf(s,va);        printf("\n");        }    va_end(va);}


It is still absolutley horrible and will probably bite you at some point.
First of all, google va_arg c++.

Second of all, never use const_cast except to get around somebody else's broken interface. In this case, you defined the interface. Define it correctly to begin with, and const_cast is completely unneccessary.

Third of all, register is a useless keyword. Forget it exists.

Fourth of all, how large is a std::string? What makes you think advancing varg [which points to format] by the size of an int would cause you to skip the first argument in your parameters?

Fifth of all, see first of all.

Sixth of all, C++ comes with all sorts of spiffy string manipulation routines. You've discovered one of them...its called std::string, and if you look Report you'll see yourself misusing one. Another useful one is std::stringstream. Between the two, most of your code is probably unneccessary. *edit: And, you know, vprintf. Which I forgot exists. Yeah, most of your code is definately unneccessary.

CM
Thanks for the advice, the reason I want to over ride printf is so that I can use its type of functionality (creating a string from a string and some variables) and then redirect this string to my GUI elements (so my logging class and the GUI event list).

A printf type solution seemed like the best way to go about this. Can I redirect the output from a vprintf to a char* / std::string so that I can use it like this or does it always head towards a stdout?
Just use:
voidmyPrintf(const char* const text, ...){  va_list va;  va_start(va, text);  char temp[1024];  vsprintf(temp, text, va);  va_end(va);  doSomethingWith(temp);}


Or even better, the safe version (if present):
voidmyPrintf(const char* const text, ...){  va_list va;  va_start(va, text);  char temp[1024];  _vsnprintf(temp, sizeof(temp), text, va);  va_end(va);  doSomethingWith(temp);}
I'm sure this won't persuade you, but there are better alternatives that varadic arguments. I remember reading someone here recently suggesting chaining operator() overloads for this kind of purpose:

using namespace std;void DealWith(const string &s){ /* your stuff here */ }class logger{private:    ostringstream os;public:    logger(){ }    logger &operator()(int i){ os << i; return *this; }    logger &operator()(const string &s){ os << s; return *this; }    // etc    logger &operator()(){ DealWith(os.str()); os.str(""); return *this; }};logger log;void f(){    log("This is the ")(35)("th logging line.")();}


Not ideal in a lot of ways, but it is type-safe, extensible and a lot less error-prone than varadics.

Of course you could also model the << insertors used by the STL and provide a custom inserter (like std::endl) that triggers that the string is complete and requires acting upon.

Just a thought.
Most appreciated, those seem significantly simpler than what I was trying to do. Thanks for the help.
Quote:Original post by EasilyConfused
I'm sure this won't persuade you, but there are better alternatives that varadic arguments. I remember reading someone here recently suggesting chaining operator() overloads for this kind of purpose:


That would probably be me. Using operator() for this is sort of a signature of mine. ;) operator<< is more language-idiomatic, but I like operator() here more for aesthetic reasons - and partly because it emphasises the idea from the problem domain of "I want all of these things to be function parameters). Instead of "chaining" operators on a "stream", we are "currying arguments". :)

That said, if you *must* have a printf-style interface, there is boost::format, which provides all the advantages you'd expect of doing things in a proper C++ way.

Just say no to variadic functions :(

This topic is closed to new replies.

Advertisement