a printf implementation

J

Jordan Abel

I have written this function and was wondering if anyone else could
point out if there's anything wrong with it. The purpose is to
substitute for printf when in a situation where low-level [beyond the
scope of this newsgroup] I/O has to be used for whatever reason, and
memory allocation cannot be used [for risk of it failing or whatever
else] - mainly it's become for me an exercise in interpreting
printf-like format strings, and i thought i'd post it here to see if
anyone can point out any bugs in it - i.e. anything that would cause
undefined behavior, and any valid format strings it might fail to
interpret [there's functionality it doesn't implement, but it should at
least pop off all the same arguments for any given valid format string
that a real printf function will.]

[is this the appropriate newsgroup for this kind of thing?]
______

#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wchar.h>

int write(int fd,char*buf,size_t len);

#if 0 /*if your system doesn't have this platform-specific function*/
static FILE * fdtab[] = { stdin, stdout, stderr, 0, 0, 0, 0, 0, 0, 0 };
int write(int fd,char *buf,size_t len) {
if(fd > 0 || !fdtab[fd]) {
return -1;
}
return fwrite(buf,1,len,fdtab[fd]);
}
#endif

#define LENGTH 64

static char buf[LENGTH];
static int pos;
static char const udigit[16] = "0123456789ABCDEF";
static char const ldigit[16] = "0123456789abcdef";
static const char *digits;

static void flush(int fd) {
write(fd,buf,pos);
pos=0;
}

static inline void eputc(int fd, int c) {
buf[pos++]=c;
if(pos==LENGTH) flush(fd);
}

static inline void eputwc(int fd, wint_t c) {
int x = wctob(c); eputc(fd,x<0?'?':x);
}

static void eputws(int fd, wchar_t *s) {
while(*s) { eputwc(fd,*s); s++; }
}

static void eputs(int fd, char *s) {
while(*s) { eputc(fd,*s); s++; }
}

static char * uimaxtoa(uintmax_t n, int base) {
static char buf[CHAR_BIT*sizeof(n)+2];
char *endptr=buf+(sizeof buf)-2;
do {
*endptr--=digits[n%base];
n/=base;
} while(n);
return endptr+1;
}

static char * pcvt(void *p) {
static char buf[sizeof(void *)*CHAR_BIT];
/* well, we can always hope this will fit - anyone know a more
* portable way to implement %p? */
sprintf(buf,"%p",p);
return buf;
}

static char * tcvt(time_t t) {
static char buf[] = "YYYY-MM-DD HH:MM:SS";
strftime(buf,sizeof buf,"%Y-%m-%d %H:%M:%S",gmtime(&t));
return buf;
}

void eprintf(int fd, const char *fmt, ...) {
int c;
va_list ap;
int islong=0;
uintmax_t uarg;
intmax_t iarg;

va_start(ap,fmt);

loop:
for(;*fmt&&*fmt!='%';fmt++)
eputc(fd,*fmt);
if(!*fmt) goto end;
if(*fmt++ == '%') {
islong=0;
digits=ldigit;
top:
c = *fmt++;
retry:
switch(c) {
case 'c':
if(islong) eputwc(fd,va_arg(ap,wint_t));
else eputc(fd,va_arg(ap,int));
break;
/* obsolete, but it doesn't conflict with anything. */
case 'D': case 'O': case 'U': case 'C': case 'S':
islong=1; c=tolower(c); goto retry;
case 'A': case 'E': case 'F': case 'G': case 'X':
digits=udigit; c = tolower(c); goto retry;
case 'd': case 'i':
switch(islong) {
case 0: iarg=va_arg(ap,int); break;
case 1: iarg=va_arg(ap,long int); break;
case 2: iarg=va_arg(ap,long long int); break;
// this is wrong on non-twos-complement systems
case 3: iarg=va_arg(ap,size_t); break;
case 4: iarg=va_arg(ap,ptrdiff_t); break;
case 5: iarg=va_arg(ap,intmax_t); break;
}
if(iarg<0) {
eputc(fd,'-');
uarg=-iarg;
} else {
uarg=iarg;
}
goto cvt;
case 'o': case 'u': case 'x':
switch(islong) {
case 0: uarg=va_arg(ap,unsigned); break;
case 1: uarg=va_arg(ap,long unsigned); break;
case 2: uarg=va_arg(ap,long long unsigned); break;
case 3: uarg=va_arg(ap,size_t); break;
// this is wrong on non-twos-complement systems.
case 4: uarg=va_arg(ap,ptrdiff_t); break;
case 5: uarg=va_arg(ap,uintmax_t); break;
}
cvt:
eputs(fd,uimaxtoa(uarg,c=='o'?8:(c=='x'?16:10)));
break;
case 'p':
eputs(fd,pcvt(va_arg(ap,void*))); break;
case 's':
if(islong)eputws(fd,va_arg(ap,wchar_t *));
else eputs(fd,va_arg(ap,char *));
break;
/* my own extensions, they print an int representing an errno value and
* a time_t, respectively. %! calls strerror(). */
case '!':
eputs(fd,strerror(va_arg(ap,int))); break;
case 'T':
eputs(fd,tcvt(va_arg(ap,time_t))); break;
case 0:
goto end;
default:
eputs(fd,"%?"); /*fall*/
case '%':
write(fd,&c,1); break;
case 'l': islong++; if(islong > 2) islong=2; goto top;
case 'j': islong=5; goto top;
case 't': islong=4; goto top;
case 'z': islong=3; goto top;
// here's the stuff we don't implement
case 'n':
switch(islong) {
case 0: (void)va_arg(ap,int *); break;
case 1: (void)va_arg(ap,long *); break;
case 2: (void)va_arg(ap,long long *); break;
case 3: (void)va_arg(ap,size_t *); break;
// this is wrong on non-twos-complement systems.
case 4: (void)va_arg(ap,ptrdiff_t *); break;
case 5: (void)va_arg(ap,intmax_t *); break;
} break;
case '*':
(void)va_arg(ap,int);
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '.': case '#': case '+': case '-': case ' ':
case '\'':
goto top;
case '$':
/* this, i can't even pretend to support - eh, well, it's a posix thing,
* rather than a C thing, anyway. */
eputs(fd,"%$?????\n");
flush(fd);
return;
/* I don't feel like messing with floating point right now */
case 'L':
islong=2; goto top;
case 'a': case 'e': case 'f': case 'g':
if(islong == 2) (void)va_arg(ap, long double);
else if(islong == 2) (void)va_arg(ap, double);
eputs(fd,"%FLT?");
break;
}
}
goto loop;
end:
flush(fd);
}
 
K

Kenneth Brody

pete said:
That should be

write(fd, (char *)&c, 1);

Given that c is of type "int", such a construct would work only on a
little-endian platform. (Or, I suppose, a platform where sizeof(int)==1.)

Better to make c a char instead, given that it's value is obtained by
dereferencing a char*.

--
+-------------------------+--------------------+-----------------------------+
| Kenneth J. Brody | www.hvcomputer.com | |
| kenbrody/at\spamcop.net | www.fptech.com | #include <std_disclaimer.h> |
+-------------------------+--------------------+-----------------------------+
Don't e-mail me at: <mailto:[email protected]>
 
B

Ben Pfaff

Jordan Abel said:
I have written this function and was wondering if anyone else could
point out if there's anything wrong with it. The purpose is to
substitute for printf when in a situation where low-level [beyond the
scope of this newsgroup] I/O has to be used for whatever reason, and
memory allocation cannot be used [for risk of it failing or whatever
else]

If you want to look at a simple, but more complete, version of
printf(), then you could look at the version that I wrote that is
incorporated in Pintos:
http://www.stanford.edu/class/cs140/pintos/pintos.tar.gz

The printf() implementation is in pintos/src/lib/stdio.c.
 
F

Flash Gordon

Jordan said:
I have written this function and was wondering if anyone else could
point out if there's anything wrong with it. The purpose is to
substitute for printf when in a situation where low-level [beyond the
scope of this newsgroup] I/O has to be used for whatever reason, and
memory allocation cannot be used [for risk of it failing or whatever
else] - mainly it's become for me an exercise in interpreting
printf-like format strings, and i thought i'd post it here to see if
anyone can point out any bugs in it - i.e. anything that would cause
undefined behavior, and any valid format strings it might fail to
interpret [there's functionality it doesn't implement, but it should at
least pop off all the same arguments for any given valid format string
that a real printf function will.]

[is this the appropriate newsgroup for this kind of thing?]

Seems a reasonable place to me, especially as you have provided a
standard C implementation of the one otherwise non-standard function you
use.

Other valid reason for writing such a function iare implementing sprintf
on an embedded system without it or implementing an extended fprintf
like function.
______

#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wchar.h>

int write(int fd,char*buf,size_t len);

#if 0 /*if your system doesn't have this platform-specific function*/
static FILE * fdtab[] = { stdin, stdout, stderr, 0, 0, 0, 0, 0, 0, 0 };
int write(int fd,char *buf,size_t len) {
if(fd > 0 || !fdtab[fd]) {
return -1;

This looks wrong to me. Don't you mean
if (fd < 0 || fd >= ((sizeof fdtab)/sizeof *fdtab)) ||
!fdtab[fd]) {
return -1;
}
return fwrite(buf,1,len,fdtab[fd]);
}
#endif

#define LENGTH 64

static char buf[LENGTH];
static int pos;
static char const udigit[16] = "0123456789ABCDEF";
static char const ldigit[16] = "0123456789abcdef";
static const char *digits;

Obviously not going for thread safety.

I'm not sure about your logic on which functions you are specifying
inline for.
static void flush(int fd) {
write(fd,buf,pos);
pos=0;
}

static inline void eputc(int fd, int c) {
buf[pos++]=c;

buf is a char array, so if c is outside the range of char (say char is
signed on your implementation) you have a problem. See below...
if(pos==LENGTH) flush(fd);
}

static inline void eputwc(int fd, wint_t c) {
int x = wctob(c); eputc(fd,x<0?'?':x);

Horribly formatting, IMHO, but that's not the problem.
wtoc returns the character in the range of unsigned char (or EOF), so if
plain char is signed this can give you a number outside the range eputc
is defined for. How about making buf an array of unsigned char?

Alternatively, use wctob and handle it fully, this (uncompiled so
probably broken) code is a start, but moving on and handling shift state
properly would be better:
int i;
char wcbuf[MB_CUR_MAX];
int nc=wctomb(wcbuf,c);
if (nc<0) {
eputc(fd,'?');
}
else {
for (i=0; i<nc; i++) {
eputc(fd,wcbuf);
}
}
}

static void eputws(int fd, wchar_t *s) {
while(*s) { eputwc(fd,*s); s++; }
}

static void eputs(int fd, char *s) {
while(*s) { eputc(fd,*s); s++; }
}

static char * uimaxtoa(uintmax_t n, int base) {
static char buf[CHAR_BIT*sizeof(n)+2];
char *endptr=buf+(sizeof buf)-2;
do {
*endptr--=digits[n%base];
n/=base;
} while(n);
return endptr+1;
}

static char * pcvt(void *p) {
static char buf[sizeof(void *)*CHAR_BIT];
/* well, we can always hope this will fit - anyone know a more
* portable way to implement %p? */
sprintf(buf,"%p",p);

Yuk. I would suggest fully implementing the %p yourself, producing a
string like "0xaabbccdd" with the length obviously dependant on
sizeof(void*). You could even check for a null pointer and output
something like "(null)" for that.
return buf;
}

static char * tcvt(time_t t) {
static char buf[] = "YYYY-MM-DD HH:MM:SS";

As a matter of style I prefer not naming a local variable the same as a
file scope variable.
strftime(buf,sizeof buf,"%Y-%m-%d %H:%M:%S",gmtime(&t));
return buf;
}

void eprintf(int fd, const char *fmt, ...) {
int c;
va_list ap;
int islong=0;
uintmax_t uarg;
intmax_t iarg;

va_start(ap,fmt);

loop:
for(;*fmt&&*fmt!='%';fmt++)
eputc(fd,*fmt);
if(!*fmt) goto end;
if(*fmt++ == '%') {
islong=0;
digits=ldigit;
top:
c = *fmt++;
retry:
switch(c) {
case 'c':
if(islong) eputwc(fd,va_arg(ap,wint_t));
else eputc(fd,va_arg(ap,int));
break;
/* obsolete, but it doesn't conflict with anything. */
case 'D': case 'O': case 'U': case 'C': case 'S':
islong=1; c=tolower(c); goto retry;
case 'A': case 'E': case 'F': case 'G': case 'X':
digits=udigit; c = tolower(c); goto retry;
case 'd': case 'i':
switch(islong) {
case 0: iarg=va_arg(ap,int); break;
case 1: iarg=va_arg(ap,long int); break;
case 2: iarg=va_arg(ap,long long int); break;
// this is wrong on non-twos-complement systems
case 3: iarg=va_arg(ap,size_t); break;

Use a typedef for the correct type which can be customised for each system?
case 4: iarg=va_arg(ap,ptrdiff_t); break;
case 5: iarg=va_arg(ap,intmax_t); break;
}
if(iarg<0) {
eputc(fd,'-');
uarg=-iarg;

This can overflow for maximum negative value of the largest signed type
on a 2s complement system.
} else {
uarg=iarg;
}
goto cvt;
case 'o': case 'u': case 'x':
switch(islong) {
case 0: uarg=va_arg(ap,unsigned); break;
case 1: uarg=va_arg(ap,long unsigned); break;
case 2: uarg=va_arg(ap,long long unsigned); break;
case 3: uarg=va_arg(ap,size_t); break;
// this is wrong on non-twos-complement systems.
case 4: uarg=va_arg(ap,ptrdiff_t); break;

Use a typedef for the correct type which can be customised for each system?
case 5: uarg=va_arg(ap,uintmax_t); break;
}
cvt:
eputs(fd,uimaxtoa(uarg,c=='o'?8:(c=='x'?16:10)));
break;
case 'p':
eputs(fd,pcvt(va_arg(ap,void*))); break;
case 's':
if(islong)eputws(fd,va_arg(ap,wchar_t *));
else eputs(fd,va_arg(ap,char *));
break;
/* my own extensions, they print an int representing an errno value and
* a time_t, respectively. %! calls strerror(). */
case '!':
eputs(fd,strerror(va_arg(ap,int))); break;
case 'T':
eputs(fd,tcvt(va_arg(ap,time_t))); break;
case 0:
goto end;
default:
eputs(fd,"%?"); /*fall*/
case '%':
write(fd,&c,1); break;

As pete pointed out, the above is wrong. Why not use your eputc
function? That's what it is there for, after all ;-)
case 'l': islong++; if(islong > 2) islong=2; goto top;
case 'j': islong=5; goto top;
case 't': islong=4; goto top;
case 'z': islong=3; goto top;
// here's the stuff we don't implement
case 'n':
switch(islong) {
case 0: (void)va_arg(ap,int *); break;
case 1: (void)va_arg(ap,long *); break;
case 2: (void)va_arg(ap,long long *); break;
case 3: (void)va_arg(ap,size_t *); break;
// this is wrong on non-twos-complement systems.
case 4: (void)va_arg(ap,ptrdiff_t *); break;
case 5: (void)va_arg(ap,intmax_t *); break;
} break;
case '*':
(void)va_arg(ap,int);
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '.': case '#': case '+': case '-': case ' ':
case '\'':
goto top;

This will mean you do something silly with a format specifier with a
field width in it.
case '$':
/* this, i can't even pretend to support - eh, well, it's a posix thing,
* rather than a C thing, anyway. */
eputs(fd,"%$?????\n");
flush(fd);
return;
/* I don't feel like messing with floating point right now */
case 'L':
islong=2; goto top;
case 'a': case 'e': case 'f': case 'g':
if(islong == 2) (void)va_arg(ap, long double);
else if(islong == 2) (void)va_arg(ap, double);
eputs(fd,"%FLT?");
break;
}
}
goto loop;

Wouldn't using a loop construct be clearer?
end:
flush(fd);

You need to use va_end when you have finished, as pete pointed out.

Not a bad start IMHO.
 
J

Jordan Abel

Jordan Abel said:
I have written this function and was wondering if anyone else could
point out if there's anything wrong with it. The purpose is to
substitute for printf when in a situation where low-level [beyond the
scope of this newsgroup] I/O has to be used for whatever reason, and
memory allocation cannot be used [for risk of it failing or whatever
else]

If you want to look at a simple, but more complete, version of
printf(), then you could look at the version that I wrote that is
incorporated in Pintos:
http://www.stanford.edu/class/cs140/pintos/pintos.tar.gz

The printf() implementation is in pintos/src/lib/stdio.c.

interesting - mine makes an effort to at least eat the appropriate
arguments for floating types, %n, etc. And it doesn't recursively call
itself for errors, because as far as i can tell it's not re-entrant
[mainly due to my manual buffering of stuff]
 
J

Jordan Abel

Jordan said:
I have written this function and was wondering if anyone else could
point out if there's anything wrong with it. The purpose is to
substitute for printf when in a situation where low-level [beyond the
scope of this newsgroup] I/O has to be used for whatever reason, and
memory allocation cannot be used [for risk of it failing or whatever
else] - mainly it's become for me an exercise in interpreting
printf-like format strings, and i thought i'd post it here to see if
anyone can point out any bugs in it - i.e. anything that would cause
undefined behavior, and any valid format strings it might fail to
interpret [there's functionality it doesn't implement, but it should at
least pop off all the same arguments for any given valid format string
that a real printf function will.]

[is this the appropriate newsgroup for this kind of thing?]

Seems a reasonable place to me, especially as you have provided a
standard C implementation of the one otherwise non-standard function you
use.

Other valid reason for writing such a function iare implementing sprintf
on an embedded system without it or implementing an extended fprintf
like function.
______

#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wchar.h>

int write(int fd,char*buf,size_t len);

#if 0 /*if your system doesn't have this platform-specific function*/
static FILE * fdtab[] = { stdin, stdout, stderr, 0, 0, 0, 0, 0, 0, 0 };
int write(int fd,char *buf,size_t len) {
if(fd > 0 || !fdtab[fd]) {
return -1;

This looks wrong to me. Don't you mean
if (fd < 0 || fd >= ((sizeof fdtab)/sizeof *fdtab)) ||
!fdtab[fd]) {
return -1;
yeah.
}
return fwrite(buf,1,len,fdtab[fd]);
}
#endif

#define LENGTH 64

static char buf[LENGTH];
static int pos;
static char const udigit[16] = "0123456789ABCDEF";
static char const ldigit[16] = "0123456789abcdef";
static const char *digits;

Obviously not going for thread safety.

I was going to add a lock, eventually. Such things are hardly standard
C. [It's not signal-safe either, but, then, what is, in stdc]
I'm not sure about your logic on which functions you are specifying
inline for.
static void flush(int fd) {
write(fd,buf,pos);
pos=0;
}

static inline void eputc(int fd, int c) {
buf[pos++]=c;

buf is a char array, so if c is outside the range of char (say char is
signed on your implementation) you have a problem. See below...
if(pos==LENGTH) flush(fd);
}

static inline void eputwc(int fd, wint_t c) {
int x = wctob(c); eputc(fd,x<0?'?':x);

Horribly formatting, IMHO, but that's not the problem.
wtoc returns the character in the range of unsigned char (or EOF), so if
plain char is signed this can give you a number outside the range eputc
is defined for. How about making buf an array of unsigned char?

that would probably be the best solution
Alternatively, use wctob and handle it fully,

The code as-is is just to do something sensible with %ls and %lc
this (uncompiled so probably broken) code is a start, but moving on
and handling shift state properly would be better:
int i;
char wcbuf[MB_CUR_MAX];
int nc=wctomb(wcbuf,c);
if (nc<0) {
eputc(fd,'?');
}
else {
for (i=0; i<nc; i++) {
eputc(fd,wcbuf);
}
}
static char * pcvt(void *p) {
static char buf[sizeof(void *)*CHAR_BIT];
/* well, we can always hope this will fit - anyone know a more
* portable way to implement %p? */
sprintf(buf,"%p",p);

Yuk. I would suggest fully implementing the %p yourself, producing a
string like "0xaabbccdd" with the length obviously dependant on
sizeof(void*). You could even check for a null pointer and output
something like "(null)" for that.


My other thought was to rely on the presence of intptr_t. I don't want
to just dump bytes in front of an 0x, since that will yield a result
that's "wrong" [for some values of "wrong"] on little-endian platforms
return buf;
}

static char * tcvt(time_t t) {
static char buf[] = "YYYY-MM-DD HH:MM:SS";

As a matter of style I prefer not naming a local variable the same as a
file scope variable.

I didn't notice that - they weren't named the same originally, but at
some point i changed the name of the global one.
Use a typedef for the correct type which can be customised for each system?

I thought about leaving this out entirely and limiting it to systems on
which size_t is the same as either unsigned, unsigned long, or unsigned
long long.
This can overflow for maximum negative value of the largest signed type
on a 2s complement system.

Can it? Even assigning to an unsigned variable? I can't think how i
_would_ do it properly. Are you sure the signed-unsigned conversion as
defined in the standard wouldn't "magically" fix this issue?

Do I have to do something silly like if(iarg!=INTMAX_MIN)? How do I deal
with systems that _aren't_ twos-complement in that case?
Use a typedef for the correct type which can be customised for each system?

yeah, yeah
As pete pointed out, the above is wrong. Why not use your eputc
function? That's what it is there for, after all ;-)

Yeah, I already fixed it - this was leftover from an earlier
implementation that didn't do buffering.
This will mean you do something silly with a format specifier with a
field width in it.

it'll mean i ignore the field width [along with those other modifiers],
won't it? "goto top" still advances to the next character in the format
string. "goto retry" stays on the same character or a reassigned one.
Wouldn't using a loop construct be clearer?

I didn't want to add another level of indentation.
You need to use va_end when you have finished, as pete pointed out.

yeah, curse this traditional-unix-like implementation where it's not a
problem.
 
B

Ben Pfaff

Jordan Abel said:
interesting - mine makes an effort to at least eat the appropriate
arguments for floating types, %n, etc. And it doesn't recursively call
itself for errors, because as far as i can tell it's not re-entrant

My implementation undoubtedly has different design goals. One of
them is to flag errors as obtrusively as possible, because it
will be used by computer science students.

I don't think the recursive calls are that interesting. They
could easily be removed if it was thought to be problematic.
 
J

Jordan Abel

My implementation undoubtedly has different design goals. One of
them is to flag errors as obtrusively as possible, because it
will be used by computer science students.

Well, my implementation as posted does flag an error when given a float
[since gcvt isn't part of the C standard AFAIK and i'm not up to writing
one just yet] but it still tries to pop it off the argument list and
cope with the rest of the string - the only thing that makes it abort is
the $ modifier, since dealing with that posix extension requires
maintaining a lot more state than the design allows for.
 
F

Flash Gordon

Jordan said:
Jordan said:
I have written this function and was wondering if anyone else could
point out if there's anything wrong with it. The purpose is to
substitute for printf when in a situation where low-level [beyond the
#define LENGTH 64

static char buf[LENGTH];
static int pos;
static char const udigit[16] = "0123456789ABCDEF";
static char const ldigit[16] = "0123456789abcdef";
static const char *digits;
Obviously not going for thread safety.

I was going to add a lock, eventually. Such things are hardly standard
C. [It's not signal-safe either, but, then, what is, in stdc]

Make buf and pos local variables in the main function, then pass them
down (possibly putting them in a stack) and you are at least a long way
along the line. After all, nothing outside needs them and you flush the
buffer at the end anyway.

static char * pcvt(void *p) {
static char buf[sizeof(void *)*CHAR_BIT];
/* well, we can always hope this will fit - anyone know a more
* portable way to implement %p? */
sprintf(buf,"%p",p);
Yuk. I would suggest fully implementing the %p yourself, producing a
string like "0xaabbccdd" with the length obviously dependant on
sizeof(void*). You could even check for a null pointer and output
something like "(null)" for that.

My other thought was to rely on the presence of intptr_t. I don't want
to just dump bytes in front of an 0x, since that will yield a result
that's "wrong" [for some values of "wrong"] on little-endian platforms

How about the DSK9009 which is little endian for integer types but big
endian for pointers? ;-)

<snip>

iarg is intmax_t
uarg is uintmax_t
Can it? Even assigning to an unsigned variable?

Yes, because you are doing the negation whilst it is still a signed
type, it only gets converted to unsigned on assignment.
> I can't think how i
_would_ do it properly.

uarg = -(uintmax_t)iarg;
This will (I believe) work because it is effectively equivalent to:
ut = iarg + UINTMAX_MAX + 1 (i.e. the conversion to unsigned)
uarg = UINTMAX_MAX + 1 - ut (the negation)
> Are you sure the signed-unsigned conversion as
defined in the standard wouldn't "magically" fix this issue?

The conversion to unsigned fixes out of range, the problem is it occurs
too late. Remember that in C the compiler does not analyse the entire
expression to work out what types it should use, it only looks at the
types of the operand(s) for the operator it is dealing with. So if the
type of the expression the unary - is applied to is a signed type, it
will be done with signed arithmetic, even if the result is then
immediately converted to unsigned.
Do I have to do something silly like if(iarg!=INTMAX_MIN)? How do I deal
with systems that _aren't_ twos-complement in that case?

Systems that are not 2s complement are not the problem. In 1s complement
and signed magnitude you don't get things like INTMAX_MIN < -INTMAX_MAX,
instead you get negative 0 (which won't cause you a problem here).
However, I believe what I've suggested is safe for all conforming
implementations.
yeah, yeah

Sorry, I went in to "comment on everything you see" mode.

This will mean you do something silly with a format specifier with a
field width in it.

it'll mean i ignore the field width [along with those other modifiers],
won't it? "goto top" still advances to the next character in the format
string. "goto retry" stays on the same character or a reassigned one.

I might well have misread the code.
I didn't want to add another level of indentation.

IMHO that is the worst argument I've seen for using goto and definitely
an argument I would reject in a formal review. I thought the indentation
shortage was solved when we moved away from punched cards, or at least
when we moved to terminals that could do 132 column displays!
yeah, curse this traditional-unix-like implementation where it's not a
problem.

I've yet to see an implementation where it would cause a failure, but
that does not mean they don't exist and will never exist in the future.
 
J

Jordan Abel

Jordan said:
Jordan Abel wrote:
I have written this function and was wondering if anyone else could
point out if there's anything wrong with it. The purpose is to
substitute for printf when in a situation where low-level [beyond the
#define LENGTH 64

static char buf[LENGTH];
static int pos;
static char const udigit[16] = "0123456789ABCDEF";
static char const ldigit[16] = "0123456789abcdef";
static const char *digits;
Obviously not going for thread safety.

I was going to add a lock, eventually. Such things are hardly standard
C. [It's not signal-safe either, but, then, what is, in stdc]

Make buf and pos local variables in the main function, then pass them
down (possibly putting them in a stack) and you are at least a long way
along the line. After all, nothing outside needs them and you flush the
buffer at the end anyway.

Actually, if i put them in main, i'll probably move the basic code for
eputc and eputs into main, somehow.

the other problem are the static buffers in the helper functions.
static char * pcvt(void *p) {
static char buf[sizeof(void *)*CHAR_BIT];
/* well, we can always hope this will fit - anyone know a more
* portable way to implement %p? */
sprintf(buf,"%p",p);
Yuk. I would suggest fully implementing the %p yourself, producing a
string like "0xaabbccdd" with the length obviously dependant on
sizeof(void*). You could even check for a null pointer and output
something like "(null)" for that.

My other thought was to rely on the presence of intptr_t. I don't want
to just dump bytes in front of an 0x, since that will yield a result
that's "wrong" [for some values of "wrong"] on little-endian platforms

How about the DSK9009 which is little endian for integer types but big
endian for pointers? ;-)

I was referring to the pointer endianness. since nothing gets converted
to a multibyte integer type in your suggestion, the endianness of such
types is irrelevant.
uarg = -(uintmax_t)iarg;
This will (I believe) work because it is effectively equivalent to:
ut = iarg + UINTMAX_MAX + 1 (i.e. the conversion to unsigned)
uarg = UINTMAX_MAX + 1 - ut (the negation)

doesn't the converted-to-unsigned iarg get converted back to signed for
the negation?

treating iarg and uarg as a 16-bit int/unsigned to make the real numbers
simpler

uarg=uninit iarg=-32768
(unsigned)iarg=32768
[int](unsigned)iarg=-32768
-[int](unsigned)iarg=UNDEF
Sorry, I went in to "comment on everything you see" mode.

no problem - i actually considered requiring that the types in question
correspond to one of the basic types.
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '.': case '#': case '+': case '-': case ' ':
case '\'':
goto top;
This will mean you do something silly with a format specifier with a
field width in it.

it'll mean i ignore the field width [along with those other modifiers],
won't it? "goto top" still advances to the next character in the format
string. "goto retry" stays on the same character or a reassigned one.

I might well have misread the code.
I didn't want to add another level of indentation.

IMHO that is the worst argument I've seen for using goto and definitely
an argument I would reject in a formal review. I thought the indentation
shortage was solved when we moved away from punched cards, or at least
when we moved to terminals that could do 132 column displays!

eh - i have a better one, actually

the end: label is needed anyway if i do va_end, because in order to add
compatibility to old varargs.h implementations i'd need to have va_end
at the same brace level as va_start, and i can't break a loop from
within a case block. I need the top: and retry: labels for reasons that
follow from how the code is arranged, and as long as those are in place
it makes no sense to demand a while(1) or for(;;) loop. there's also the
fact that the body of the loop is too long and it's an unconditional
loop, and a bare } at the bottom will require going back up to check
what level it's at.
 
C

Chris Torek

I have written this function and was wondering if anyone else could
point out if there's anything wrong with it. ...

Others have noted potential problems. I will add one more that
has nothing to do with the code itself.

In general, whenever you write any variadic function, you should
actually write *two* functions. The first one is the variadic
function itself, which is very short:

#include <stdarg.h>

int like_printf(const char *fmt, ...) {
int ret;
va_list ap;

va_start(fmt, ap);
ret = vlike_printf(fmt, ap);
va_end(fmt);
return ret;
}

The second function you write, named "v"-whatever (in this case,
vlike_printf), contains the guts of the code and uses the stdarg.h
va_list parameter.

There are a few cases in which two (or more) passes over the varying
arguments are required, or at least convenient. For these cases,
you can either rely on C99's va_copy() in the "v" version of the
function, or pass multiple copies of the va_list "ap" value (which
is a burden to other callers, but avoids the C99 dependency).
 
M

Michael Wojcik

Jordan said:
I have written this function and was wondering if anyone else could
point out if there's anything wrong with it. The purpose is to
substitute for printf when in a situation where low-level [beyond the
scope of this newsgroup] I/O has to be used for whatever reason, and
memory allocation cannot be used [for risk of it failing or whatever
else] ...

Other valid reason for writing such a function iare implementing sprintf
on an embedded system without it or implementing an extended fprintf
like function.

Or as a replacement for the existing *printf functions, if they're
deficient. I have to deal with a number of pre-standard [v]snprintf
implementations, with the usual flaw: they return -1 both for
formatting errors and for insufficient destination space. That
alone makes replacing them worthwhile, and I've been collecting
references to alternate implementations with an eye to doing just
that.

--
Michael Wojcik (e-mail address removed)

Maybe, but it can't compete with _SNA Formats_ for intricate plot
twists. "This format is used only when byte 5, bit 1 is set to 1
(i.e., when generalized PIU trace data is included)" - brilliant!
 
B

Ben Pfaff

Or as a replacement for the existing *printf functions, if they're
deficient. I have to deal with a number of pre-standard [v]snprintf
implementations, with the usual flaw: they return -1 both for
formatting errors and for insufficient destination space. That
alone makes replacing them worthwhile, and I've been collecting
references to alternate implementations with an eye to doing just
that.

Gnulib, at http://savannah.gnu.org/p/gnulib, has among its
modules an implementation that will serve this purpose. It's not
strictly conforming C.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top