a printf implementation

Discussion in 'C Programming' started by Jordan Abel, Dec 7, 2005.

  1. Jordan Abel

    Jordan Abel Guest

    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);
    }
    Jordan Abel, Dec 7, 2005
    #1
    1. Advertising

  2. Jordan Abel

    pete Guest

    Jordan Abel wrote:

    > va_start(ap,fmt);


    There should probably be a
    va_end(ap);
    somewhere.

    --
    pete
    pete, Dec 7, 2005
    #2
    1. Advertising

  3. Jordan Abel

    pete Guest

    Jordan Abel wrote:

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


    > int c;


    > write(fd,&c,1);


    That should be

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

    --
    pete
    pete, Dec 7, 2005
    #3
  4. Jordan Abel

    pete Guest

    pete wrote:
    >
    > Jordan Abel wrote:
    >
    > > int write(int fd,char*buf,size_t len);

    >
    > > int c;

    >
    > > write(fd,&c,1);

    >
    > That should be
    >
    > write(fd, (char *)&c, 1);


    .... or maybe c should be type char.

    --
    pete
    pete, Dec 7, 2005
    #4
  5. pete wrote:
    >
    > Jordan Abel wrote:
    >
    > > int write(int fd,char*buf,size_t len);

    >
    > > int c;

    >
    > > write(fd,&c,1);

    >
    > 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:>
    Kenneth Brody, Dec 7, 2005
    #5
  6. Jordan Abel

    Jordan Abel Guest

    On 2005-12-07, pete <> wrote:
    > pete wrote:
    >>
    >> Jordan Abel wrote:
    >>
    >> > int write(int fd,char*buf,size_t len);

    >>
    >> > int c;

    >>
    >> > write(fd,&c,1);

    >>
    >> That should be
    >>
    >> write(fd, (char *)&c, 1);

    >
    > ... or maybe c should be type char.


    Or maybe this is something i forgot to convert to eputc(fd,c);

    I probably wouldn't have spotted that.
    Jordan Abel, Dec 7, 2005
    #6
  7. Jordan Abel

    Ben Pfaff Guest

    Jordan Abel <> writes:

    > 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.
    --
    "To get the best out of this book, I strongly recommend that you read it."
    --Richard Heathfield
    Ben Pfaff, Dec 7, 2005
    #7
  8. Jordan Abel

    Flash Gordon Guest

    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
    > 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.
    --
    Flash Gordon
    Living in interesting times.
    Although my email address says spam, it is real and I read it.
    Flash Gordon, Dec 7, 2005
    #8
  9. Jordan Abel

    Jordan Abel Guest

    On 2005-12-07, Ben Pfaff <> wrote:
    > Jordan Abel <> writes:
    >
    >> 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]
    Jordan Abel, Dec 7, 2005
    #9
  10. Jordan Abel

    Jordan Abel Guest

    On 2005-12-07, Flash Gordon <> wrote:
    > 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
    >> 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.

    >> // 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?


    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.

    >> if(iarg<0) {
    >> eputc(fd,'-');
    >> uarg=-iarg;

    >
    > 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?

    >> // 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?


    yeah, yeah

    >> 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 ;-)


    Yeah, I already fixed it - this was leftover from an earlier
    implementation that didn't do buffering.

    >> 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.

    >> goto loop;

    >
    > Wouldn't using a loop construct be clearer?


    I didn't want to add another level of indentation.

    >
    >> end:
    >> flush(fd);

    >
    > 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.
    Jordan Abel, Dec 7, 2005
    #10
  11. Jordan Abel

    Ben Pfaff Guest

    Jordan Abel <> writes:

    > On 2005-12-07, Ben Pfaff <> wrote:
    >> Jordan Abel <> writes:
    >>
    >> 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


    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.
    --
    "To get the best out of this book, I strongly recommend that you read it."
    --Richard Heathfield
    Ben Pfaff, Dec 7, 2005
    #11
  12. Jordan Abel

    Jordan Abel Guest

    On 2005-12-07, Ben Pfaff <> wrote:
    > Jordan Abel <> writes:
    >
    >> On 2005-12-07, Ben Pfaff <> wrote:
    >>> Jordan Abel <> writes:
    >>>
    >>> 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

    >
    > 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.

    > I don't think the recursive calls are that interesting. They
    > could easily be removed if it was thought to be problematic.
    Jordan Abel, Dec 7, 2005
    #12
  13. Jordan Abel

    Flash Gordon Guest

    Jordan Abel wrote:
    > On 2005-12-07, Flash Gordon <> wrote:
    >> 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


    <snip>

    >>> #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.

    <snip>

    >>> 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

    >>> if(iarg<0) {
    >>> eputc(fd,'-');
    >>> uarg=-iarg;

    >> 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?


    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.

    >>> // 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?

    >
    > yeah, yeah


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

    <snip>

    >>> 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.

    >>> goto loop;

    >> Wouldn't using a loop construct be clearer?

    >
    > 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!

    >>> end:
    >>> flush(fd);

    >> 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.


    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.
    --
    Flash Gordon
    Living in interesting times.
    Although my email address says spam, it is real and I read it.
    Flash Gordon, Dec 7, 2005
    #13
  14. Jordan Abel

    Jordan Abel Guest

    On 2005-12-07, Flash Gordon <> wrote:
    > Jordan Abel wrote:
    >> On 2005-12-07, Flash Gordon <> wrote:
    >>> 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

    >
    > <snip>
    >
    >>>> #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.

    >
    > <snip>
    >
    >>>> 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.

    >
    > > 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)


    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

    >> yeah, yeah

    >
    > 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.
    >
    >>>> goto loop;
    >>> Wouldn't using a loop construct be clearer?

    >>
    >> 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.
    Jordan Abel, Dec 7, 2005
    #14
  15. Jordan Abel

    Chris Torek Guest

    In article <>
    Jordan Abel <> wrote:
    >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).
    --
    In-Real-Life: Chris Torek, Wind River Systems
    Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
    email: forget about it http://web.torek.net/torek/index.html
    Reading email is like searching for food in the garbage, thanks to spammers.
    Chris Torek, Dec 8, 2005
    #15
  16. In article <-gordon.me.uk>, Flash Gordon <> writes:
    > 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
    > > 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

    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!
    Michael Wojcik, Dec 9, 2005
    #16
  17. Jordan Abel

    Ben Pfaff Guest

    (Michael Wojcik) writes:

    > 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.
    --
    int main(void){char p[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.\
    \n",*q="kl BIcNBFr.NKEzjwCIxNJC";int i=sizeof p/2;char *strchr();int putchar(\
    );while(*q){i+=strchr(p,*q++)-p;if(i>=(int)sizeof p)i-=sizeof p-1;putchar(p\
    );}return 0;}
    Ben Pfaff, Dec 9, 2005
    #17
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. ben
    Replies:
    4
    Views:
    600
    Martin Ambuhl
    Jun 26, 2004
  2. whatluo

    (void) printf vs printf

    whatluo, May 26, 2005, in forum: C Programming
    Replies:
    29
    Views:
    1,225
  3. Michael Tsang
    Replies:
    32
    Views:
    1,082
    Richard Bos
    Mar 1, 2010
  4. azza

    printf affects following printf/s

    azza, Oct 17, 2010, in forum: C Programming
    Replies:
    0
    Views:
    423
  5. guru
    Replies:
    8
    Views:
    265
Loading...

Share This Page