a printf implementation

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Jordan Abel

    #1

    a printf implementation

    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,siz e_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,le n,fdtab[fd]);
    }
    #endif

    #define LENGTH 64

    static char buf[LENGTH];
    static int pos;
    static char const udigit[16] = "0123456789ABCD EF";
    static char const ldigit[16] = "0123456789abcd ef";
    static const char *digits;

    static void flush(int fd) {
    write(fd,buf,po s);
    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(uintma x_t n, int base) {
    static char buf[CHAR_BIT*sizeof (n)+2];
    char *endptr=buf+(si zeof 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,si zeof buf,"%Y-%m-%d %H:%M:%S",gmtim e(&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_ar g(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,uimaxt oa(uarg,c=='o'? 8:(c=='x'?16:10 )));
    break;
    case 'p':
    eputs(fd,pcvt(v a_arg(ap,void*) )); break;
    case 's':
    if(islong)eputw s(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,strerr or(va_arg(ap,in t))); break;
    case 'T':
    eputs(fd,tcvt(v a_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);
    }
  • pete

    #2
    Re: a printf implementation

    Jordan Abel wrote:
    [color=blue]
    > va_start(ap,fmt );[/color]

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

    --
    pete

    Comment

    • pete

      #3
      Re: a printf implementation

      Jordan Abel wrote:
      [color=blue]
      > int write(int fd,char*buf,siz e_t len);[/color]
      [color=blue]
      > int c;[/color]
      [color=blue]
      > write(fd,&c,1);[/color]

      That should be

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

      --
      pete

      Comment

      • pete

        #4
        Re: a printf implementation

        pete wrote:[color=blue]
        >
        > Jordan Abel wrote:
        >[color=green]
        > > int write(int fd,char*buf,siz e_t len);[/color]
        >[color=green]
        > > int c;[/color]
        >[color=green]
        > > write(fd,&c,1);[/color]
        >
        > That should be
        >
        > write(fd, (char *)&c, 1);[/color]

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

        --
        pete

        Comment

        • Kenneth Brody

          #5
          Re: a printf implementation

          pete wrote:[color=blue]
          >
          > Jordan Abel wrote:
          >[color=green]
          > > int write(int fd,char*buf,siz e_t len);[/color]
          >[color=green]
          > > int c;[/color]
          >[color=green]
          > > write(fd,&c,1);[/color]
          >
          > That should be
          >
          > write(fd, (char *)&c, 1);[/color]

          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:ThisIsA SpamTrap@gmail. com>


          Comment

          • Jordan Abel

            #6
            Re: a printf implementation

            On 2005-12-07, pete <pfiland@mindsp ring.com> wrote:[color=blue]
            > pete wrote:[color=green]
            >>
            >> Jordan Abel wrote:
            >>[color=darkred]
            >> > int write(int fd,char*buf,siz e_t len);[/color]
            >>[color=darkred]
            >> > int c;[/color]
            >>[color=darkred]
            >> > write(fd,&c,1);[/color]
            >>
            >> That should be
            >>
            >> write(fd, (char *)&c, 1);[/color]
            >
            > ... or maybe c should be type char.[/color]

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

            I probably wouldn't have spotted that.

            Comment

            • Ben Pfaff

              #7
              Re: a printf implementation

              Jordan Abel <jmabel@purdue. edu> writes:
              [color=blue]
              > 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][/color]

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

              Comment

              • Jordan Abel

                #8
                Re: a printf implementation

                On 2005-12-07, Ben Pfaff <blp@cs.stanfor d.edu> wrote:[color=blue]
                > Jordan Abel <jmabel@purdue. edu> writes:
                >[color=green]
                >> 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][/color]
                >
                > 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.tar.gz
                >
                > The printf() implementation is in pintos/src/lib/stdio.c.[/color]

                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]

                Comment

                • Flash Gordon

                  #9
                  Re: a printf implementation

                  Jordan Abel wrote:[color=blue]
                  > 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?][/color]

                  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.
                  [color=blue]
                  > ______
                  >
                  > #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,siz e_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;[/color]

                  This looks wrong to me. Don't you mean
                  if (fd < 0 || fd >= ((sizeof fdtab)/sizeof *fdtab)) ||
                  !fdtab[fd]) {
                  return -1;[color=blue]
                  > }
                  > return fwrite(buf,1,le n,fdtab[fd]);
                  > }
                  > #endif
                  >
                  > #define LENGTH 64
                  >
                  > static char buf[LENGTH];
                  > static int pos;
                  > static char const udigit[16] = "0123456789ABCD EF";
                  > static char const ldigit[16] = "0123456789abcd ef";
                  > static const char *digits;[/color]

                  Obviously not going for thread safety.

                  I'm not sure about your logic on which functions you are specifying
                  inline for.
                  [color=blue]
                  > static void flush(int fd) {
                  > write(fd,buf,po s);
                  > pos=0;
                  > }
                  >
                  > static inline void eputc(int fd, int c) {
                  > buf[pos++]=c;[/color]

                  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...
                  [color=blue]
                  > if(pos==LENGTH) flush(fd);
                  > }
                  >
                  > static inline void eputwc(int fd, wint_t c) {
                  > int x = wctob(c); eputc(fd,x<0?'? ':x);[/color]

                  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[i]);
                  }
                  }[color=blue]
                  > }
                  >
                  > 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(uintma x_t n, int base) {
                  > static char buf[CHAR_BIT*sizeof (n)+2];
                  > char *endptr=buf+(si zeof 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);[/color]

                  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.
                  [color=blue]
                  > return buf;
                  > }
                  >
                  > static char * tcvt(time_t t) {
                  > static char buf[] = "YYYY-MM-DD HH:MM:SS";[/color]

                  As a matter of style I prefer not naming a local variable the same as a
                  file scope variable.
                  [color=blue]
                  > strftime(buf,si zeof buf,"%Y-%m-%d %H:%M:%S",gmtim e(&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_ar g(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;[/color]

                  Use a typedef for the correct type which can be customised for each system?
                  [color=blue]
                  > 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;[/color]

                  This can overflow for maximum negative value of the largest signed type
                  on a 2s complement system.
                  [color=blue]
                  > } 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;[/color]

                  Use a typedef for the correct type which can be customised for each system?
                  [color=blue]
                  > case 5: uarg=va_arg(ap, uintmax_t); break;
                  > }
                  > cvt:
                  > eputs(fd,uimaxt oa(uarg,c=='o'? 8:(c=='x'?16:10 )));
                  > break;
                  > case 'p':
                  > eputs(fd,pcvt(v a_arg(ap,void*) )); break;
                  > case 's':
                  > if(islong)eputw s(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,strerr or(va_arg(ap,in t))); break;
                  > case 'T':
                  > eputs(fd,tcvt(v a_arg(ap,time_t ))); break;
                  > case 0:
                  > goto end;
                  > default:
                  > eputs(fd,"%?"); /*fall*/
                  > case '%':
                  > write(fd,&c,1); break;[/color]

                  As pete pointed out, the above is wrong. Why not use your eputc
                  function? That's what it is there for, after all ;-)
                  [color=blue]
                  > 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;[/color]

                  This will mean you do something silly with a format specifier with a
                  field width in it.
                  [color=blue]
                  > 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;[/color]

                  Wouldn't using a loop construct be clearer?
                  [color=blue]
                  > end:
                  > flush(fd);[/color]

                  You need to use va_end when you have finished, as pete pointed out.
                  [color=blue]
                  > }[/color]

                  Not a bad start IMHO.
                  --
                  Flash Gordon
                  Living in interesting times.
                  Although my email address says spam, it is real and I read it.

                  Comment

                  • Jordan Abel

                    #10
                    Re: a printf implementation

                    On 2005-12-07, Flash Gordon <spam@flash-gordon.me.uk> wrote:[color=blue]
                    > Jordan Abel wrote:[color=green]
                    >> 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?][/color]
                    >
                    > 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.
                    >[color=green]
                    >> ______
                    >>
                    >> #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,siz e_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;[/color]
                    >
                    > This looks wrong to me. Don't you mean
                    > if (fd < 0 || fd >= ((sizeof fdtab)/sizeof *fdtab)) ||
                    > !fdtab[fd]) {
                    > return -1;[/color]

                    yeah.
                    [color=blue][color=green]
                    >> }
                    >> return fwrite(buf,1,le n,fdtab[fd]);
                    >> }
                    >> #endif
                    >>
                    >> #define LENGTH 64
                    >>
                    >> static char buf[LENGTH];
                    >> static int pos;
                    >> static char const udigit[16] = "0123456789ABCD EF";
                    >> static char const ldigit[16] = "0123456789abcd ef";
                    >> static const char *digits;[/color]
                    >
                    > Obviously not going for thread safety.[/color]

                    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]
                    [color=blue]
                    > I'm not sure about your logic on which functions you are specifying
                    > inline for.
                    >[color=green]
                    >> static void flush(int fd) {
                    >> write(fd,buf,po s);
                    >> pos=0;
                    >> }
                    >>
                    >> static inline void eputc(int fd, int c) {
                    >> buf[pos++]=c;[/color]
                    >
                    > 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...
                    >[color=green]
                    >> if(pos==LENGTH) flush(fd);
                    >> }
                    >>
                    >> static inline void eputwc(int fd, wint_t c) {
                    >> int x = wctob(c); eputc(fd,x<0?'? ':x);[/color]
                    >
                    > 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?[/color]

                    that would probably be the best solution
                    [color=blue]
                    > Alternatively, use wctob and handle it fully,[/color]

                    The code as-is is just to do something sensible with %ls and %lc
                    [color=blue]
                    > 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[i]);
                    > }
                    > }[/color]
                    [color=blue][color=green]
                    >> 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);[/color]
                    >
                    > 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.[/color]

                    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
                    [color=blue][color=green]
                    >> return buf;
                    >> }
                    >>
                    >> static char * tcvt(time_t t) {
                    >> static char buf[] = "YYYY-MM-DD HH:MM:SS";[/color]
                    >
                    > As a matter of style I prefer not naming a local variable the same as a
                    > file scope variable.[/color]

                    I didn't notice that - they weren't named the same originally, but at
                    some point i changed the name of the global one.
                    [color=blue][color=green]
                    >> // this is wrong on non-twos-complement systems
                    >> case 3: iarg=va_arg(ap, size_t); break;[/color]
                    >
                    > Use a typedef for the correct type which can be customised for each system?[/color]

                    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.
                    [color=blue][color=green]
                    >> if(iarg<0) {
                    >> eputc(fd,'-');
                    >> uarg=-iarg;[/color]
                    >
                    > This can overflow for maximum negative value of the largest signed type
                    > on a 2s complement system.[/color]

                    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?
                    [color=blue][color=green]
                    >> // this is wrong on non-twos-complement systems.
                    >> case 4: uarg=va_arg(ap, ptrdiff_t); break;[/color]
                    >
                    > Use a typedef for the correct type which can be customised for each system?[/color]

                    yeah, yeah
                    [color=blue][color=green]
                    >> case '%':
                    >> write(fd,&c,1); break;[/color]
                    >
                    > As pete pointed out, the above is wrong. Why not use your eputc
                    > function? That's what it is there for, after all ;-)[/color]

                    Yeah, I already fixed it - this was leftover from an earlier
                    implementation that didn't do buffering.
                    [color=blue][color=green]
                    >> 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;[/color]
                    >
                    > This will mean you do something silly with a format specifier with a
                    > field width in it.[/color]

                    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.
                    [color=blue][color=green]
                    >> goto loop;[/color]
                    >
                    > Wouldn't using a loop construct be clearer?[/color]

                    I didn't want to add another level of indentation.
                    [color=blue]
                    >[color=green]
                    >> end:
                    >> flush(fd);[/color]
                    >
                    > You need to use va_end when you have finished, as pete pointed out.[/color]

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

                    Comment

                    • Ben Pfaff

                      #11
                      Re: a printf implementation

                      Jordan Abel <jmabel@purdue. edu> writes:
                      [color=blue]
                      > On 2005-12-07, Ben Pfaff <blp@cs.stanfor d.edu> wrote:[color=green]
                      >> Jordan Abel <jmabel@purdue. edu> 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.tar.gz
                      >>
                      >> The printf() implementation is in pintos/src/lib/stdio.c.[/color]
                      >
                      > 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[/color]

                      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

                      Comment

                      • Jordan Abel

                        #12
                        Re: a printf implementation

                        On 2005-12-07, Ben Pfaff <blp@cs.stanfor d.edu> wrote:[color=blue]
                        > Jordan Abel <jmabel@purdue. edu> writes:
                        >[color=green]
                        >> On 2005-12-07, Ben Pfaff <blp@cs.stanfor d.edu> wrote:[color=darkred]
                        >>> Jordan Abel <jmabel@purdue. edu> 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.tar.gz
                        >>>
                        >>> The printf() implementation is in pintos/src/lib/stdio.c.[/color]
                        >>
                        >> 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[/color]
                        >
                        > 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.[/color]

                        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.
                        [color=blue]
                        > I don't think the recursive calls are that interesting. They
                        > could easily be removed if it was thought to be problematic.[/color]

                        Comment

                        • Flash Gordon

                          #13
                          Re: a printf implementation

                          Jordan Abel wrote:[color=blue]
                          > On 2005-12-07, Flash Gordon <spam@flash-gordon.me.uk> wrote:[color=green]
                          >> Jordan Abel wrote:[color=darkred]
                          >>> 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[/color][/color][/color]

                          <snip>
                          [color=blue][color=green][color=darkred]
                          >>> #define LENGTH 64
                          >>>
                          >>> static char buf[LENGTH];
                          >>> static int pos;
                          >>> static char const udigit[16] = "0123456789ABCD EF";
                          >>> static char const ldigit[16] = "0123456789abcd ef";
                          >>> static const char *digits;[/color]
                          >> Obviously not going for thread safety.[/color]
                          >
                          > 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][/color]

                          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>
                          [color=blue][color=green][color=darkred]
                          >>> 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);[/color]
                          >> 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.[/color]
                          >
                          > 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[/color]

                          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
                          [color=blue][color=green][color=darkred]
                          >>> if(iarg<0) {
                          >>> eputc(fd,'-');
                          >>> uarg=-iarg;[/color]
                          >> This can overflow for maximum negative value of the largest signed type
                          >> on a 2s complement system.[/color]
                          >
                          > Can it? Even assigning to an unsigned variable?[/color]

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

                          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)
                          [color=blue]
                          > Are you sure the signed-unsigned conversion as
                          > defined in the standard wouldn't "magically" fix this issue?[/color]

                          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.
                          [color=blue]
                          > 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?[/color]

                          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 .
                          [color=blue][color=green][color=darkred]
                          >>> // this is wrong on non-twos-complement systems.
                          >>> case 4: uarg=va_arg(ap, ptrdiff_t); break;[/color]
                          >> Use a typedef for the correct type which can be customised for each system?[/color]
                          >
                          > yeah, yeah[/color]

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

                          <snip>
                          [color=blue][color=green][color=darkred]
                          >>> 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;[/color]
                          >> This will mean you do something silly with a format specifier with a
                          >> field width in it.[/color]
                          >
                          > 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.[/color]

                          I might well have misread the code.
                          [color=blue][color=green][color=darkred]
                          >>> goto loop;[/color]
                          >> Wouldn't using a loop construct be clearer?[/color]
                          >
                          > I didn't want to add another level of indentation.[/color]

                          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!
                          [color=blue][color=green][color=darkred]
                          >>> end:
                          >>> flush(fd);[/color]
                          >> You need to use va_end when you have finished, as pete pointed out.[/color]
                          >
                          > yeah, curse this traditional-unix-like implementation where it's not a
                          > problem.[/color]

                          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.

                          Comment

                          • Jordan Abel

                            #14
                            Re: a printf implementation

                            On 2005-12-07, Flash Gordon <spam@flash-gordon.me.uk> wrote:[color=blue]
                            > Jordan Abel wrote:[color=green]
                            >> On 2005-12-07, Flash Gordon <spam@flash-gordon.me.uk> wrote:[color=darkred]
                            >>> 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[/color][/color]
                            >
                            > <snip>
                            >[color=green][color=darkred]
                            >>>> #define LENGTH 64
                            >>>>
                            >>>> static char buf[LENGTH];
                            >>>> static int pos;
                            >>>> static char const udigit[16] = "0123456789ABCD EF";
                            >>>> static char const ldigit[16] = "0123456789abcd ef";
                            >>>> static const char *digits;
                            >>> Obviously not going for thread safety.[/color]
                            >>
                            >> 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][/color]
                            >
                            > 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.[/color]

                            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.
                            [color=blue]
                            >
                            > <snip>
                            >[color=green][color=darkred]
                            >>>> 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.[/color]
                            >>
                            >> 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[/color]
                            >
                            > How about the DSK9009 which is little endian for integer types but big
                            > endian for pointers? ;-)[/color]

                            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.
                            [color=blue]
                            >[color=green]
                            > > I can't think how i
                            >> _would_ do it properly.[/color]
                            >
                            > 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)[/color]

                            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
                            [color=blue][color=green]
                            >> yeah, yeah[/color]
                            >
                            > Sorry, I went in to "comment on everything you see" mode.[/color]

                            no problem - i actually considered requiring that the types in question
                            correspond to one of the basic types.
                            [color=blue][color=green][color=darkred]
                            >>>> 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.[/color]
                            >>
                            >> 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.[/color]
                            >
                            > I might well have misread the code.
                            >[color=green][color=darkred]
                            >>>> goto loop;
                            >>> Wouldn't using a loop construct be clearer?[/color]
                            >>
                            >> I didn't want to add another level of indentation.[/color]
                            >
                            > 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![/color]

                            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.

                            Comment

                            • Chris Torek

                              #15
                              Re: a printf implementation

                              In article <slrndpd496.egg .jmabel@random. yi.org>
                              Jordan Abel <jmabel@purdue. edu> wrote:[color=blue]
                              >I have written this function and was wondering if anyone else could
                              >point out if there's anything wrong with it. ...[/color]

                              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(con st char *fmt, ...) {
                              int ret;
                              va_list ap;

                              va_start(fmt, ap);
                              ret = vlike_printf(fm t, 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.

                              Comment

                              Working...