Voici ma participation pour le niveau 3. Mon code est encore assez brouillon et ne gère pas les formats "%e" et "%g". Il gère par contre l'affichage des flottants (avec la possibilité de spécifier le nombre souhaité de chiffre après la virgule) et ce, peut importe leur taille. Cependant, étant donné que je converti les nombres flottants en base 10, les valeurs affichées peuvent être imprécises (et cela ce remarque d'autant plus que le nombre est grand ou petit) :
#include <float.h>
#include <limits.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
static double zmodf(double, double *);
static int print_double(double, unsigned);
static int print_int(size_t, unsigned);
static int print_str(char const *);
static int conversion(char const *, va_list *, int *);
static double
zmodf(double n, double *pi)
{
double i = 0.;
double tmp = n;
double exp;
int digit;
if (n < 1.) {
*pi = i;
return n;
}
for (exp = 0.; tmp >= FLT_RADIX; exp += 1.)
tmp /= FLT_RADIX;
exp = pow(FLT_RADIX, exp);
digit = 0;
do {
i += (int)tmp * exp;
tmp -= (int)tmp;
tmp *= FLT_RADIX;
exp /= FLT_RADIX;
} while (exp >= 1. && digit++ < DBL_MANT_DIG);
*pi = i;
return n - i;
}
static int
print_double(double n, unsigned precision)
{
double i, f;
int exp;
int nb = 0;
if (n < 0.) {
putchar('-');
n = -n;
++nb;
}
f = zmodf(n, &i);
exp = (int)log10(i);
i /= pow(10., (double)exp);
while (i > 0. && exp-- >= 0) {
putchar((int)i + '0');
i -= (int)i;
i *= 10.;
++nb;
}
if (precision) {
putchar('.');
++nb;
}
while (precision-- > 0) {
f *= 10.;
putchar((int)f + '0');
f -= (int)f;
++nb;
}
return nb;
}
static int
print_int(size_t n, unsigned base)
{
static char buffer[sizeof (long) * CHAR_BIT];
char *s = buffer;
int nb = 0;
if (n == 0) {
putchar('0');
return 1;
}
while (n != 0) {
*s++ = "0123456789ABCDEF"[n % base];
n /= base;
}
do {
putchar(*--s);
++nb;
} while (s != buffer);
return nb;
}
static int
print_str(char const *s)
{
int nb;
for (nb = 0; s[nb] != '\0'; ++nb)
putchar(s[nb]);
return nb;
}
static int
conversion(char const *fmt, va_list *ap, int *nb)
{
char const *t = fmt;
unsigned precision = 6;
int large = 0;
unsigned base = 10;
long tmp;
for (;;) {
switch (*fmt) {
case '%' :
++fmt;
putchar('%');
++*nb;
goto conversion_end;
case '.' :
++fmt;
precision = (unsigned)strtoul(fmt, (char **)&fmt, 10);
break;
case 'f' :
++fmt;
*nb += print_double(va_arg(*ap, double), precision);
goto conversion_end;
case 'l' :
++fmt;
large = 1;
break;
case 'd' :
case 'i' :
++fmt;
if (large)
tmp = va_arg(*ap, long);
else
tmp = va_arg(*ap, int);
if (tmp < 0) {
putchar('-');
++*nb;
tmp = -tmp;
}
*nb += print_int((size_t)tmp, base);
goto conversion_end;
case 'x' :
base = 18;
case 'o' :
base -= 2;
case 'u' :
++fmt;
if (large)
*nb += print_int(va_arg(*ap, unsigned long), base);
else
*nb += print_int(va_arg(*ap, unsigned), base);
goto conversion_end;
case 's' :
++fmt;
*nb += print_str(va_arg(*ap, char const *));
goto conversion_end;
default :
++fmt;
goto conversion_end;
}
}
conversion_end:
return (int)(fmt - t);
}
int
zprintf(char const *fmt, ...)
{
va_list ap;
int nb = 0;
va_start(ap, fmt);
while (*fmt != '\0') {
switch (*fmt) {
case '%' :
fmt += conversion(fmt + 1, &ap, &nb);
break;
default :
putchar(*fmt);
++nb;
break;
}
++fmt;
}
va_end(ap);
return nb;
}
int
main(void)
{
zprintf("%% %f %s %ld\n", 6789.54467, "pouf !", 6789089L);
return 0;
}
À noter que je n'ai pas recoder toutes les fonctions de la bibliothèque standard dont j'avais besoin, mise à part modf (l'exercice était intéressant ).
If a conversion specification is invalid, the behavior is undefined.
Mais je pense que gérer les cas de formateurs indéfinis peut être intéressant dans le cas de l'exo.
Au moins afficher un message d'erreur comme quoi le formateur est indéfini et que la conversion ne peut pas continuer.
Bon, il faudrait faire ça au niveau du compilateur et pas en runtime, mais nous on peut rien y faire. On se contente de ce qu'on est en mesure de faire.
@Taurre : ton code est plutôt bien, j'ai juste relevé une petite coquille pour %x :
case 'x' :
base = 18;
C'est pas la base 16 plutôt ?
Pour le coup il a rusé : le déroulement du programme continue dans le branchement 'o' (vu qu'il n'y a pas de branchements inconditionnels) et soustrait 2 à cette valeur.
@Taurre : ton code est plutôt bien, j'ai juste relevé une petite coquille pour %x :
case 'x' :
base = 18;
C'est pas la base 16 plutôt ?
Pour le coup il a rusé : le déroulement du programme continue dans le branchement 'o' (vu qu'il n'y a pas de branchements inconditionnels) et soustrait 2 à cette valeur.
Exact
Cela m'évite de répéter le code du format "%u" pour les formats "%o" et "%x"
Rien à voir avec le sujet, mais pour la culture générale :
Citation : Mr21
D'ailleurs je n'avais pas remarque mais gcc donne des warnings pour printf, c'est la seule fonction qui est favoriser comme ça ou pas?
Voir __attribute__((format(printf,m,n))), extension GCC (idem pour scanf()). Si tu écris une fonction qui utilises printf() en interne, n'hésite pas à t'en servir, ta fonction aura aussi le droit à ce traitement de faveur
... J'avoue que j'ai bon y réfléchir je ne vois pour l'instant pas comment faire cela. Je serais curieux de savoir comment ils s'en sortent à Epitech par exemple
Pour parler franchement, la plupart des gens ne pensent pas à la manière dont cela est stocké, et vont partir de leur postulat que c'est en base 10, et donc l'afficher à base de multiplications successives par 10.
Très peu se servent de la mantisse et de l'exponentiation pour écrire leur résultat.
C’est ce que je me disais, on pourrait se contenter d’afficher la partie entière (obtenue avec une conversion en entier) avec la fonction pour afficher des entiers, puis afficher la partie décimale par multiplications successives de la base. En faisant gaffe à l’exposant quand même.
Pour les flottants, récupérer tout ça à base de multiplications/divisions successives donne un bon résultat ?
Jouer avec la mantisse et compagnie, je l'ai déjà fait, c'est assez reulou, pas envie de recommencer.
.
0xbffff384: [20]
0xbffff384: [20]
010: [4]
0 010: [25]
Characters: a A : [16]
Characters: a A : [41]
Decimals: 1977 650000: [21]
Decimals: 1977 650000: [62]
Preceding with blanks: 1977 : [34]
Preceding with blanks: 1977 : [96]
Preceding with zeros: 0000001977 : [33]
Preceding with zeros: 1977 : [129]
Some different radixes: 100 64 144 0x64 0144 : [45]
Some different radixes: 100 64 144 0x64 0144 : [174]
floats: 3.14 +3e+00 3.141600E+00 : [33]
floats: f e E : [188]
Width trick: 10 : [19]
Width trick: 10 : [207]
A string : [9]
A string : [216]
|1234567890123|: [15]
|1234567890123|: [231]
| abcdefghijk|: [15]
| a|: [246]
|abcdefghi |: [15]
|a |: [261]
| abcdefghij|: [15]
| a|: [276]
| abcdefghijk|: [15]
| a|: [291]
| cdefghijk|: [15]
| c|: [306]
Edit: Ah, aussi, tu pourrais fournir un code compilable avec tout ce qui s'en suit... genre printf.h et un main valide histoire qu'on ai pas à se prendre la tête avec ces broutilles...
Quelqu'un pour répondre à ma question juste avant ? =D
Jouer avec la mantisse et compagnie, je l'ai déjà fait, c'est assez reulou, pas envie de recommencer.
Tu as encore ton code ? Je serais curieux de voir comment être plus précis
Citation : Maëlan
C’est ce que je me disais, on pourrait se contenter d’afficher la partie entière (obtenue avec une conversion en entier) avec la fonction pour afficher des entiers, puis afficher la partie décimale par multiplications successives de la base. En faisant gaffe à l’exposant quand même.
La conversion en entier ne me paraît pas une bonne solution, même avec un longlong on ne fait pas long feu (0x1.FFFFFEp127 au maximum pour un float selon la norme IEEE 754).
- Pour le premier test (0 en précision), gcc m'affiche un warning. J'ai toutefois rajouté une condition pour gérer ce cas.
- Pour le quatrième test, j'avais oublié une condition.
- Sinon, pour les erreurs de taille, j'avais oublié de remettre la variable statique à zéro à chaque appel de printf.
- Par ailleurs, tu m'as fait pensé aux wchar_t que j'avais complètement zappé.
Taurre > Merde... je vais voir comment je vais faire du coup.
Pour le coup de jouer avec les bits, j'ai dit 'recommencer' parce que je retrouve plus le code justement...
lucas > Pour la précision 0, c'est dit par la norme. Si la précision est 0 et et que le nombre est 0 on n'affiche rien.
Pour les tests, ça a l'air de fonctionner là.
Par ailleurs, tu m'as fait pensé aux wchar_t que j'avais complètement zappé.
Attention que ton implémentation n'est pas bonne de ce côté. En effet, il ne faut pas perdre de vue qu'un caractère large doit être converti en un ou plusieurs multicaractères. C'est par exemple le cas si le jeu de caractère de l'environnement d'exécution est l'UTF-8. Il est donc nécessaire de passer par les fonctions de transcodages afin d'obtenir une chaîne de multicaractères et d'afficher cette dernière.
Si l'on souhaite rester conforme au C89, cela peut se faire à l'aide de la macroconstante MB_CUR_MAX et de la fonction wcstombs.
static int
printwcharstring(conf *c) {
int size = 0, i;
char *mbs;
size_t n;
if (c->data.ls == NULL) {
c->data.ls = L"<null>";
size = 6;
}
while (c->data.ls[size] != L'\0' && (unsigned)size < (unsigned)c->prec)
++size;
if (!(c->f & F_LEFTADJUSTED))
PRINT(c->field-- > size, ' ', c->dst);
mbs = malloc(size * MB_CUR_MAX + 1);
if (mbs == NULL)
return RET_FAIL;
if ((n = wcstombs(mbs, c->data.ls, size * MB_CUR_MAX)) == (size_t)-1)
return RET_FAIL;
mbs[n] = '\0';
for (i = 0; i < n; ++i)
PUTC(mbs[i], c->dst);
PRINT(c->field-- > size, ' ', c->dst);
return RET_OK;
}
Salut, je participe en niveau 2 pour l'instant, car je ne vois pas comment afficher les nombres à virgule flottante (j'aurai juste à me documenter vite fait sur le net et j'edit ce post).
void numberToStr(int number, char base, char majuscule)
{
char charset[17] = "0123456789abcdef";
char buf[10]; //strlen("4294967295") = 10
int lenght;
int temp;
temp = 0;
lenght = 0;
if(majuscule)
{
*(unsigned long*)&charset[10] = 0x44434241; // pour écrire ABCD en litte endian seulement !!
*(unsigned short*)&charset[14] = 0x4645; // et pour écrire EF en little endian aussi
}
do
{
temp = number % base;
temp = (temp < 0 ? -temp : temp);
buf[lenght] = charset[temp];
number /= base;
lenght++;
}
while(number != 0);
while(lenght != 0)
{
lenght--;
fwrite(&buf[lenght], 1, 1, stdout);
}
}
/* Je préfère cette fonction car elle affiche vraiment la forme binaire (négative ou pas) */
void printBin(int number)
{
char displayZero; // permet d'éviter d'afficher les zero inutile
int i;
displayZero = 0;
for(i = 31; i >= 0; i--)
{
if(number & (1 << i))
{
fwrite("1", 1, 1, stdout);
displayZero = 1;
}
else if(displayZero != 0)
{
fwrite("0", 1, 1, stdout);
}
}
}
void zPrintf(char* str, ...)
{
va_list argsList;
unsigned int i;
char* tmp;
i = 0;
va_start(argsList, str);
while(str[i] != 0)
{
if(str[i] == '%')
{
switch(str[i+1])
{
case 'b':
{
printBin(va_arg(argsList, int));
//numberToStr(va_arg(argsList, int), 2);
i++;
break;
}
case 'd':
case 'i':
{
numberToStr(va_arg(argsList, int), 10, 0);
i++;
break;
}
case 'u':
{
numberToStr(va_arg(argsList, unsigned int), 10, 0);
i++;
break;
}
case 'X':
{
numberToStr(va_arg(argsList, unsigned int), 16, 1);
i++;
break;
}
case 'x':
{
numberToStr(va_arg(argsList, unsigned int), 16, 0);
i++;
break;
}
case 'o':
{
numberToStr(va_arg(argsList, unsigned int), 8, 0);
i++;
break;
}
case 's':
{
tmp = va_arg(argsList, char*);
fwrite(tmp, strlen(tmp), 1, stdout);
i++;
break;
}
case 'c':
{
fwrite(&va_arg(argsList, char), 1, 1, stdout);
i++;
break;
}
default:
{
fwrite(&str[i], 1, 1, stdout);
}
}
}
else
{
fwrite(&str[i], 1, 1, stdout);
}
i++;
}
va_end(argsList);
}
lucas > t'as un souci avec ça : testprintf("%#.0x", 0);
Je crois que je vais laisser mon code tel quel et laisser les flottants de côté.
Ou alors je vais taper sur la division/multiplication, c'est trop reulou comme truc. =D
J'le posterai quand je l'aurai nettoyé.
J’ai essayé de respecter au maximum ma page de manuel.
J’ai codé une seule fonction générique (vgnprintf) qui fait tout le boulot principal (avec des fonctions annexes pour écrire un entier, un flottant …), après on n’a plus qu’à coder facilement toutes les autres fonctions (familles sprintf et fprintf) en se basant sur cette dernière. J’ai pas (encore) codé as[n]printf, mais ça peut se faire aussi facilement. On peut même s’en servir pour écrire dans des conteneurs perso.
Je gère :
les flags ;
la taille du champ ([édit] y compris avec "*") ;
la précision ([édit] y compris avec "*") ;
les modificateurs de type ;
les conversions suivantes : entiers, pointeurs, caractères et chaînes de caractères (sauf caractères étendus), [édit] et "%n".
Il me reste à implémenter :
les flottants (comme tout le monde ) ;
les caractères et chaînes de caractères étendus (pas encore les connaissances nécessaires) ;
la conversion "%n" ;
les trucs du genre "%*i" ou"%5$i" (la flemme) .
D’autre part, je ne vérifie pas la validité du format (je ne vérifie pas si les paramètres sont dans l’ordre, cohérents, etc.). Je vais corriger ça.
Édit : Nouvelle version du code, avec rajout de fonctionnalités (listes ci-dessus mises à jour). La validité du format est désormais vérifiée (respect de l’ordre des différents champs, rejet des modificateurs de type invalides, début de gestion des erreurs). Également correction d’un bogue avec le flag '0'.
header
/**
*** printf.h
***
*** module: printf − header file
*** function: imitation of the ‘printf’ functions family.
*** author: Maëlan (aka Maëlan44)
*** (see < http://www.siteduzero.com/membres-294-232877.html >)
***
*** last modified: 2012/05/27, 11h30
***
**/
#ifndef INCLUDED_PRINTF_2012_05_25_17_30_MM
#define INCLUDED_PRINTF_2012_05_25_17_30_MM
#include <stdarg.h>
/* More header! */
#include <stdint.h> /* uintmax_t, intmax_t, SIZE_MAX */
#include <stddef.h> /* size_t, ptrdiff_t */
#include <sys/types.h> /* ssize_t */
#include <wchar.h> /* wchar_t, wint_t */
#include <ctype.h> /* isdigit(), toupper() */
#include <string.h> /* strlen() */
#include <stdio.h> /* FILE, putc() */
/* The “length modifier” of a conversion (see ‘man 3 printf’). */
typedef enum {
PRINTTYPE_hh,
PRINTTYPE_h,
PRINTTYPE_DEFAULT,
PRINTTYPE_l,
PRINTTYPE_ll,
PRINTTYPE_L,
PRINTTYPE_j,
PRINTTYPE_z,
PRINTTYPE_t
} PrintType;
/* Options controlling the output of a conversion (see ‘man 3 printf’). */
typedef struct {
size_t width; /* (minimum) field width (0: no width specified) */
size_t precis; /* precision */
PrintType type; /* … */
_Bool alt :1, /* '#' flag (use alternate form) */
zpad :1, /* '0' flag (pad with zero digits) */
alignl :1, /* '-' flag (align to the left) */
space :1, /* ' ' flag (print a space for positive numbers) */
plus :1, /* '+' flag (print a plus sign for positive numbers) */
/* additional boolean informations for internal use */
cap :1, /* Write uppercase letters (for hexa)? */
neg :1, /* Is the number negative? */
hasPrecis :1; /* Has a precision been specified? */
} PrintOpt;
#define PRINTOPT_DEFAULTINIT \
{ .type = PRINTTYPE_DEFAULT } /* all others members to zero */
/* Generic ‘printf’ context: the buffer to write into (with any real type),
* informations about it, and the current conversion options. */
typedef struct {
void* p; /* generic buffer to print into (can be ‘char*’, ‘FILE*’…) */
size_t i, /* number of characters already written */
n; /* maximal number of characters to write */
PrintOpt opt; /* … */
} PrintFrame;
/* Type of pointer to a function capable of writing one character in the buffer;
* depending on the behaviour wanted, such a function must or must not write the
* character if i >= n, but in all cases it must increment i. Those functions
* are used to achieve genericity, they are called by all the generic functions
* to write a character. */
typedef void (*PrintcF)(PrintFrame*, wint_t);
/* The core (most generic) ‘printf’ function. */
int vgnprintf(PrintcF, void* buf, size_t n, const char* fmt, va_list);
/* All the standard ‘printf’ functions. */
int vsnprintf(char* dest, size_t, const char* fmt, va_list);
int vsprintf(char* dest, const char* fmt, va_list);
int vfprintf(FILE* dest, const char* fmt, va_list);
int vprintf(const char* fmt, va_list);
int snprintf(char* dest, size_t, const char* fmt, ...);
int sprintf(char* dest, const char* fmt, ...);
int fprintf(FILE* dest, const char* fmt, ...);
int printf(const char* fmt, ...);
#endif
source (ancienne version)
</span>
/**
*** printf.c
***
*** module: printf − source file
*** function: imitation of the ‘printf’ functions family.
*** author: Maëlan (aka Maëlan44)
*** (see < http://www.siteduzero.com/membres-294-232877.html >)
***
*** last modified: 2012/05/27, 12h30
***
**/
#include "printf.h"
/** Functions fetching an argument passed via ‘...’, depending on the type
** passed, which is specified by the length modifier …
**/
/* … for an unsigned integer; */
static uintmax_t vgetUint(va_list* ap, PrintType type) {
switch(type) {
case PRINTTYPE_hh: return (unsigned char) va_arg(*ap, int);
case PRINTTYPE_h: return (unsigned short) va_arg(*ap, int);
/*case PRINTTYPE_hh: return va_arg(ap, unsigned char);
case PRINTTYPE_h: return va_arg(ap, unsigned short);*/
case PRINTTYPE_DEFAULT: return va_arg(*ap, unsigned int);
case PRINTTYPE_l: return va_arg(*ap, unsigned long);
case PRINTTYPE_ll: return va_arg(*ap, unsigned long long);
case PRINTTYPE_j: return va_arg(*ap, uintmax_t);
case PRINTTYPE_z: return va_arg(*ap, size_t);
case PRINTTYPE_t: return va_arg(*ap, ptrdiff_t);
default: return 0;
}
}
/* … for a signed integer; */
static intmax_t vgetInt(va_list* ap, PrintType type) {
switch(type) {
case PRINTTYPE_hh: return (signed char) va_arg(*ap, int);
case PRINTTYPE_h: return (signed short) va_arg(*ap, int);
/*case PRINTTYPE_hh: return va_arg(ap, signed char);
case PRINTTYPE_h: return va_arg(ap, signed short);*/
case PRINTTYPE_DEFAULT: return va_arg(*ap, signed int);
case PRINTTYPE_l: return va_arg(*ap, signed long);
case PRINTTYPE_ll: return va_arg(*ap, signed long long);
case PRINTTYPE_j: return va_arg(*ap, intmax_t);
case PRINTTYPE_z: return va_arg(*ap, ssize_t);
case PRINTTYPE_t: return va_arg(*ap, ptrdiff_t);
default: return 0;
}
}
/* … for a floating number; */
static long double vgetFloat(va_list* ap, PrintType type) {
switch(type) {
case PRINTTYPE_DEFAULT: return va_arg(*ap, double);
case PRINTTYPE_L: return va_arg(*ap, long double);
default: return 0.0;
}
}
/* … for a character; */
static wint_t vgetChar(va_list* ap, PrintType type) {
switch(type) {
case PRINTTYPE_DEFAULT: return va_arg(*ap, int);
case PRINTTYPE_l: return va_arg(*ap, wint_t);
default: return 0;
}
}
/* … for a character string (basically an array of the appropriate character
* type). */
static const void* vgetString(va_list* ap, PrintType type) {
switch(type) {
case PRINTTYPE_DEFAULT: return va_arg(*ap, const char*);
case PRINTTYPE_l: return va_arg(*ap, const wchar_t*);
default: return NULL;
}
}
/*#define vgetString(ap, type) va_arg((ap), char*)
#define vgetWString(ap, type) va_arg((ap), wchar_t*)*/
/* Read an (unsigned) decimal integer in the character string pointed to, and
* return its value. Increment the character pointer so it points on the first
* non-digit character. */
static unsigned int readUint(const char** sp) {
unsigned int r;
for(r = 0; isdigit(**sp); ++*sp)
r = r*10 + **sp-'0';
return r;
}
/** Functions writing the data on the buffer with the conversion options …
**/
/* … for an unsigned integer; */
static void gnprintUint(PrintcF printc, PrintFrame* buf, uintmax_t n, unsigned int base) {
static const char digits[36] = "0123456789abcdefghijklmnopqrstuvwxyz";
char tmp[256];
size_t i, j;
/* default precision for integers (write just the required digits) */
if(!buf->opt.hasPrecis)
buf->opt.precis = 1;
/* The '0' flag is ignored if the '-' flag or a precision is present. */
if(buf->opt.zpad && !buf->opt.alignl && !buf->opt.hasPrecis)
/* To pad with '0', we use the mechanism of precision, which controls
the minimal number of digits to write (width-1 because of the sign
character). */
buf->opt.precis = (buf->opt.width)? buf->opt.width-1 : 0;
else
buf->opt.zpad = 0;
/* digits */
for(i = 0; n || i < buf->opt.precis; ++i) {
tmp[i] = digits[n % base];
if(buf->opt.cap)
tmp[i] = toupper(tmp[i]);
n /= base;
}
/* prefix */
if(buf->opt.alt) {
if(base==16)
tmp[i++] = (buf->opt.cap)? 'X' : 'x';
if(base==16 || base==8)
tmp[i++] = '0';
}
/* sign */
if(buf->opt.neg)
tmp[i++] = '-';
else if(buf->opt.plus)
tmp[i++] = '+';
else if(buf->opt.space)
tmp[i++] = ' ';
else if(buf->opt.zpad) /* if we pad with '0' and no sign character is */
tmp[i++] = '0'; /* written, we add a '0' to fulfil the width. */
/* left padding */
if(!buf->opt.alignl)
while(buf->opt.width-- > i)
printc(buf, ' ');
/* writing the number itself, with its eventual sign */
j = i; /* save the length, because we need it later for right padding */
while(i--)
printc(buf, tmp[i]);
/* right padding */
if(buf->opt.alignl)
while(buf->opt.width-- > j)
printc(buf, ' ');
}
/* … for a signed integer: */
static void gnprintInt(PrintcF printc, PrintFrame* buf, intmax_t n, unsigned int base) {
if(n<0) {
buf->opt.neg = 1;
n = -n;
}
gnprintUint(printc, buf, n, base);
}
/* (Output style for a floating number.) */
typedef enum {
PRINTSTYLE_SCI, /* corresponds to "%e" */
PRINTSTYLE_NATURAL, /* corresponds to "%f" */
PRINTSTYLE_MIX, /* corresponds to "%g" */
PRINTSTYLE_HEX /* corresponds to "%a" */
} PrintFloatStyle;
/* … for a floating number; */
static void gnprintFloat(PrintcF printc, PrintFrame* buf, long double f, PrintFloatStyle style) {
/* … */
;
}
/* … for a wide character string; */
static void gnprintWString(PrintcF printc, PrintFrame* buf, const wchar_t* s) {
/* … */
;
}
/* … for a character string in general (including a wide character string). */
static void gnprintString(PrintcF printc, PrintFrame* buf, const char* s) {
if(buf->opt.type == PRINTTYPE_l)
gnprintWString(printc, buf, (const wchar_t*)s);
if(!s)
s = "(null)";
size_t i;
/* default precision for strings (write all the characters) */
if(!buf->opt.hasPrecis)
buf->opt.precis = SIZE_MAX;
/* left padding */
if(buf->opt.width && !buf->opt.alignl) {
size_t len = strlen(s);
if(buf->opt.hasPrecis && buf->opt.precis < len)
len = buf->opt.precis;
while(buf->opt.width-- > len)
printc(buf, ' ');
}
/* the string itself */
for(i = 0; s[i] && i < buf->opt.precis; ++i)
printc(buf, s[i]);
/* right padding */
if(buf->opt.width && buf->opt.alignl)
while(buf->opt.width-- > i)
printc(buf, ' ');
}
/** The core (most generic) ‘printf’ function.
**/
int vgnprintf(PrintcF printc, void* p, size_t n, const char* fmt, va_list ap) {
if(!p || !n || !fmt)
return 0;
PrintFrame buf = { p, 0, n, PRINTOPT_DEFAULTINIT };
for(; *fmt; ++fmt) {
/* Output a normal character. */
if(*fmt != '%')
printc(&buf, *fmt);
/* Output a formated conversion. */
else {
buf.opt = (PrintOpt) PRINTOPT_DEFAULTINIT;
/* Format analysis */
again:
switch(*++fmt) {
/* Output flags */
case '#':
buf.opt.alt = 1;
goto again;
case '0':
buf.opt.zpad = 1;
goto again;
case '-':
buf.opt.alignl = 1;
goto again;
case ' ':
buf.opt.space = 1;
goto again;
case '+':
buf.opt.plus = 1;
goto again;
/* Field width */
//case '0':
case '1':
case '2':
case '3':
case '4':
case '5': /* case powaa! */
case '6':
case '7':
case '8':
case '9':
buf.opt.width = readUint(&fmt);
--fmt;
goto again;
/* Precision */
case '.':
++fmt;
buf.opt.precis = readUint(&fmt);
--fmt;
buf.opt.hasPrecis = 1;
goto again;
/* Argument type (“length modifier”) */
case 'h':
--buf.opt.type; /* /!\ unsecure (assuming correct format) */
goto again;
case 'l':
++buf.opt.type; /* /!\ unsecure (assuming correct format) */
goto again;
case 'L':
buf.opt.type = PRINTTYPE_L;
goto again;
case 'j':
buf.opt.type = PRINTTYPE_j;
goto again;
case 'z':
buf.opt.type = PRINTTYPE_z;
goto again;
case 't':
buf.opt.type = PRINTTYPE_t;
goto again;
/* Conversions */
case 'd':
case 'i':
gnprintInt(printc, &buf, vgetInt(&ap, buf.opt.type), 10);
break;
case 'u':
gnprintUint(printc, &buf, vgetUint(&ap, buf.opt.type), 10);
break;
case 'o':
gnprintUint(printc, &buf, vgetUint(&ap, buf.opt.type), 010);
break;
case 'X':
buf.opt.cap = 1;
case 'x':
gnprintUint(printc, &buf, vgetUint(&ap, buf.opt.type), 0x10);
break;
case 'E':
buf.opt.cap = 1;
case 'e':
gnprintFloat(printc, &buf, vgetFloat(&ap, buf.opt.type), PRINTSTYLE_SCI);
break;
case 'F':
buf.opt.cap = 1;
case 'f':
gnprintFloat(printc, &buf, vgetFloat(&ap, buf.opt.type), PRINTSTYLE_NATURAL);
break;
case 'G':
buf.opt.cap = 1;
case 'g':
gnprintFloat(printc, &buf, vgetFloat(&ap, buf.opt.type), PRINTSTYLE_MIX);
break;
case 'A':
buf.opt.cap = 1;
case 'a':
gnprintFloat(printc, &buf, vgetFloat(&ap, buf.opt.type), PRINTSTYLE_HEX);
break;
case 'c':
printc(&buf, vgetChar(&ap, buf.opt.type));
break;
case 's':
gnprintString(printc, &buf, vgetString(&ap, buf.opt.type));
break;
case 'p':
buf.opt.alt = 1;
gnprintUint(printc, &buf, (uintmax_t)(uintptr_t)va_arg(ap, void*), 16);
break;
case '%':
printc(&buf, '%');
break;
default:
break;
}
}
}
return buf.i;
}
/** Functions writing a character on the buffer of a specific type. See the
** comments about the ‘F_gnprintc’ type in <printf.h> for more details.
**/
/* Function for use with ‘snprintf’: write a character on a character string, if
* the size does not exceed the maximal size.
* We should code an unsecure version of this function for use with ‘sprintf’
* (called ‘sprintChar’, and not verifying the size before writing), but since
* a ‘size_t’ integer is big enough to index any byte in the memory, this
* function with the size ‘SIZE_MAX’ will fit. */
static void snprintChar(PrintFrame* buf, wint_t c) {
if(buf->i < buf->n)
((char*)buf->p)[buf->i] = (char)c;
++buf->i;
}
/* Function for use with ‘fprintf’: write a charadter on a file. We do not need
* a secure version since ‘fnprintf’ does not exist, and moreover calling a
* secure version with the size ‘SIZE_MAX’ could not fit, because we might want
* to write more than ‘SIZE_MAX’ bytes into the disk (). */
static void fprintChar(PrintFrame* buf, wint_t c) {
putc(c, buf->p);
++buf->i;
}
/** At least, all the standard ‘printf’ functions.
**/
int vsnprintf(char* dest, size_t n, const char* fmt, va_list ap) {
int r = vgnprintf(snprintChar, dest, n, fmt, ap);
if((size_t)r < n)
dest[r] = '\0';
return r;
}
int vsprintf(char* dest, const char* fmt, va_list ap) {
return vsnprintf(dest, SIZE_MAX, fmt, ap);
}
int snprintf(char* dest, size_t n, const char* fmt, ...) {
va_list ap;
int r;
va_start(ap, fmt);
r = vsnprintf(dest, n, fmt, ap);
va_end(ap);
return r;
}
int sprintf(char* dest, const char* fmt, ...) {
va_list ap;
int r;
va_start(ap, fmt);
r = vsprintf(dest, fmt, ap);
va_end(ap);
return r;
}
int vfprintf(FILE* f, const char* fmt, va_list ap) {
return vgnprintf(fprintChar, f, SIZE_MAX, fmt, ap);
}
int vprintf(const char* fmt, va_list ap) {
return vfprintf(stdout, fmt, ap);
}
int fprintf(FILE* f, const char* fmt, ...) {
va_list ap;
int r;
va_start(ap, fmt);
r = vfprintf(f, fmt, ap);
va_end(ap);
return r;
}
int printf(const char* fmt, ...) {
va_list ap;
int r;
va_start(ap, fmt);
r = vprintf(fmt, ap);
va_end(ap);
return r;
}
Source (version à jour)
/**
*** printf.c
***
*** module: printf − source file
*** function: imitation of the ‘printf’ functions family.
*** author: Maëlan (aka Maëlan44)
*** (see < http://www.siteduzero.com/membres-294-232877.html >)
***
*** last modified: 2012/05/27, 12h30
***
**/
#include "printf.h"
/** Functions fetching an argument passed via ‘...’, depending on the type
** passed, which is specified by the length modifier …
**/
/* … for an unsigned integer; */
static uintmax_t vgetUint(va_list* ap, PrintType type) {
switch(type) {
case PRINTTYPE_hh: return (unsigned char) va_arg(*ap, int);
case PRINTTYPE_h: return (unsigned short) va_arg(*ap, int);
case PRINTTYPE_DEFAULT: return va_arg(*ap, unsigned int);
case PRINTTYPE_l: return va_arg(*ap, unsigned long);
case PRINTTYPE_L: /* non standard (L modifier for integers) */
case PRINTTYPE_ll: return va_arg(*ap, unsigned long long);
case PRINTTYPE_j: return va_arg(*ap, uintmax_t);
case PRINTTYPE_z: return va_arg(*ap, size_t);
case PRINTTYPE_t: return va_arg(*ap, ptrdiff_t);
//default: return 0;
}
}
/* … for a signed integer; */
static intmax_t vgetInt(va_list* ap, PrintType type) {
switch(type) {
case PRINTTYPE_hh: return (signed char) va_arg(*ap, int);
case PRINTTYPE_h: return (signed short) va_arg(*ap, int);
case PRINTTYPE_DEFAULT: return va_arg(*ap, signed int);
case PRINTTYPE_l: return va_arg(*ap, signed long);
case PRINTTYPE_L: /* non standard (L modifier for integers) */
case PRINTTYPE_ll: return va_arg(*ap, signed long long);
case PRINTTYPE_j: return va_arg(*ap, intmax_t);
case PRINTTYPE_z: return va_arg(*ap, ssize_t);
case PRINTTYPE_t: return va_arg(*ap, ptrdiff_t);
//default: return 0;
}
}
/* … for a floating number; */
static long double vgetFloat(va_list* ap, PrintType type) {
switch(type) {
case PRINTTYPE_DEFAULT: return va_arg(*ap, double);
case PRINTTYPE_L: return va_arg(*ap, long double);
default: return 0.0;
}
}
/* … for a character; */
static wint_t vgetChar(va_list* ap, PrintType type) {
switch(type) {
case PRINTTYPE_DEFAULT: return va_arg(*ap, int);
case PRINTTYPE_l: return va_arg(*ap, wint_t);
default: return 0;
}
}
/* … for a character string (basically an array of the appropriate character
* type). */
static const void* vgetString(va_list* ap, PrintType type) {
switch(type) {
case PRINTTYPE_DEFAULT: return va_arg(*ap, const char*);
case PRINTTYPE_l: return va_arg(*ap, const wchar_t*);
default: return NULL;
}
}
/* Read an (unsigned) decimal integer in the character string pointed to, and
* return its value. Increment the character pointer so it points on the first
* non-digit character. */
static unsigned int readUint(const char** sp, va_list* ap) {
if(**sp == '*') {
++*sp;
return va_arg(*ap, int);
}
unsigned int r;
for(r = 0; isdigit(**sp); ++*sp)
r = r*10 + **sp-'0';
return r;
}
/** Functions writing the data on the buffer with the conversion options …
**/
/* … for an unsigned integer; */
static void gnprintUint(PrintcF printc, PrintFrame* buf, uintmax_t n, unsigned int base) {
static const char digits[36] = "0123456789abcdefghijklmnopqrstuvwxyz";
char tmp[256];
size_t i, j;
/* default precision for integers (write just the required digits) */
if(!buf->opt.hasPrecis)
buf->opt.precis = 1;
/* The '0' flag is ignored if the '-' flag or a precision is present. */
if(buf->opt.zpad && !buf->opt.alignl && !buf->opt.hasPrecis)
/* To pad with '0', we use the mechanism of precision, which controls
the minimal number of digits to write (width-1 because of the sign
character). */
buf->opt.precis = (buf->opt.width)? buf->opt.width-1 : 0;
else
buf->opt.zpad = 0;
/* digits */
for(i = 0; n || i < buf->opt.precis; ++i) {
tmp[i] = digits[n % base];
if(buf->opt.cap)
tmp[i] = toupper(tmp[i]);
n /= base;
}
/* prefix */
if(buf->opt.alt) {
if(base==16)
tmp[i++] = (buf->opt.cap)? 'X' : 'x';
if(base==16 || base==8)
tmp[i++] = '0';
}
/* sign */
if(buf->opt.neg)
tmp[i++] = '-';
else if(buf->opt.plus)
tmp[i++] = '+';
else if(buf->opt.space)
tmp[i++] = ' ';
else if(buf->opt.zpad && i<buf->opt.width)
tmp[i++] = '0'; /* if we pad with '0' and no sign character is */
/* written, we add a '0' to fulfil the width. */
/* left padding */
if(!buf->opt.alignl)
while(buf->opt.width-- > i)
printc(buf, ' ');
/* writing the number itself, with its eventual sign */
j = i; /* save the length, because we need it later for right padding */
while(i--)
printc(buf, tmp[i]);
/* right padding */
if(buf->opt.alignl)
while(buf->opt.width-- > j)
printc(buf, ' ');
}
/* … for a signed integer: */
static void gnprintInt(PrintcF printc, PrintFrame* buf, intmax_t n, unsigned int base) {
if(n<0) {
buf->opt.neg = 1;
n = -n;
}
gnprintUint(printc, buf, n, base);
}
/* (Output style for a floating number.) */
typedef enum {
PRINTSTYLE_SCI, /* corresponds to "%e" */
PRINTSTYLE_NATURAL, /* corresponds to "%f" */
PRINTSTYLE_MIX, /* corresponds to "%g" */
PRINTSTYLE_HEX /* corresponds to "%a" */
} PrintFloatStyle;
/* … for a floating number; */
static void gnprintFloat(PrintcF printc, PrintFrame* buf, long double f, PrintFloatStyle style) {
/* … */
;
}
/* … for a wide character string; */
static void gnprintWString(PrintcF printc, PrintFrame* buf, const wchar_t* s) {
/* … */
;
}
/* … for a character string in general (including a wide character string). */
static void gnprintString(PrintcF printc, PrintFrame* buf, const char* s) {
if(buf->opt.type == PRINTTYPE_l)
gnprintWString(printc, buf, (const wchar_t*)s);
static const char null[] = "(null)";
size_t i;
if(!s)
s = null;
/* default precision for strings (write all the characters) */
if(!buf->opt.hasPrecis)
buf->opt.precis = SIZE_MAX;
/* left padding */
if(buf->opt.width && !buf->opt.alignl) {
size_t len = strlen(s);
if(buf->opt.hasPrecis && buf->opt.precis < len)
len = buf->opt.precis;
while(buf->opt.width-- > len)
printc(buf, ' ');
}
/* the string itself */
for(i = 0; s[i] && i < buf->opt.precis; ++i)
printc(buf, s[i]);
/* right padding */
if(buf->opt.width && buf->opt.alignl)
while(buf->opt.width-- > i)
printc(buf, ' ');
}
/** The core (most generic) ‘printf’ function.
**/
int vgnprintf(PrintcF printc, void* p, size_t n, const char* fmt, va_list ap) {
if(!p || !n || !fmt)
return 0;
PrintFrame buf = { p, 0, n, PRINTOPT_DEFAULTINIT };
for(; *fmt; ++fmt) {
/* Output a normal character. */
if(*fmt != '%')
printc(&buf, *fmt);
/* Output a formated conversion (format analysis). */
else {
const char* fmtBegin = fmt; /* (see below, when invalid format) */
buf.opt = (PrintOpt) PRINTOPT_DEFAULTINIT;
/* Output flags */
flagsAgain:
switch(*++fmt) {
case '#':
buf.opt.alt = 1;
goto flagsAgain;
case '0':
buf.opt.zpad = 1;
goto flagsAgain;
case '-':
buf.opt.alignl = 1;
goto flagsAgain;
case ' ':
buf.opt.space = 1;
goto flagsAgain;
case '+':
buf.opt.plus = 1;
goto flagsAgain;
default:
break;
}
/* Field width */
buf.opt.width = readUint(&fmt, &ap);
/* Precision */
if(*fmt == '.') {
++fmt;
buf.opt.precis = readUint(&fmt, &ap);
buf.opt.hasPrecis = 1;
}
/* Argument type (“length modifier”) */
switch(*fmt++) {
case 'h':
/*if(*fmt == 'h') {
buf.opt.type = PRINTTYPE_hh;
++fmt;
}
else
buf.opt.type = PRINTTYPE_h;*/
buf.opt.type = (*fmt == 'h' && ++fmt)? PRINTTYPE_hh : PRINTTYPE_h;
break;
case 'l':
/*if(*fmt == 'l') {
buf.opt.type = PRINTTYPE_ll;
++fmt;
}
else
buf.opt.type = PRINTTYPE_l;*/
buf.opt.type = (*fmt == 'l' && ++fmt)? PRINTTYPE_ll : PRINTTYPE_l;
break;
case 'L':
buf.opt.type = PRINTTYPE_L;
break;
case 'j':
buf.opt.type = PRINTTYPE_j;
break;
case 'z':
buf.opt.type = PRINTTYPE_z;
break;
case 't':
buf.opt.type = PRINTTYPE_t;
break;
default:
--fmt;
break;
}
/* Conversions */
switch(*fmt) {
case 'd':
case 'i':
gnprintInt(printc, &buf, vgetInt(&ap, buf.opt.type), 10);
break;
case 'u':
gnprintUint(printc, &buf, vgetUint(&ap, buf.opt.type), 10);
break;
case 'o':
gnprintUint(printc, &buf, vgetUint(&ap, buf.opt.type), 010);
break;
case 'X':
buf.opt.cap = 1;
case 'x':
gnprintUint(printc, &buf, vgetUint(&ap, buf.opt.type), 0x10);
break;
case 'E':
buf.opt.cap = 1;
case 'e':
gnprintFloat(printc, &buf, vgetFloat(&ap, buf.opt.type), PRINTSTYLE_SCI);
break;
case 'F':
buf.opt.cap = 1;
case 'f':
gnprintFloat(printc, &buf, vgetFloat(&ap, buf.opt.type), PRINTSTYLE_NATURAL);
break;
case 'G':
buf.opt.cap = 1;
case 'g':
gnprintFloat(printc, &buf, vgetFloat(&ap, buf.opt.type), PRINTSTYLE_MIX);
break;
case 'A':
buf.opt.cap = 1;
case 'a':
gnprintFloat(printc, &buf, vgetFloat(&ap, buf.opt.type), PRINTSTYLE_HEX);
break;
case 'c':
printc(&buf, vgetChar(&ap, buf.opt.type));
break;
case 's':
gnprintString(printc, &buf, vgetString(&ap, buf.opt.type));
break;
case 'p':
buf.opt.alt = 1;
gnprintUint(printc, &buf, (uintmax_t)(uintptr_t)va_arg(ap, void*), 16);
break;
case 'n':
switch(buf.opt.type) {
case PRINTTYPE_hh:
* va_arg(ap, char*) = buf.i; break;
case PRINTTYPE_h:
* va_arg(ap, short*) = buf.i; break;
case PRINTTYPE_DEFAULT:
* va_arg(ap, int*) = buf.i; break;
case PRINTTYPE_l:
* va_arg(ap, long*) = buf.i; break;
case PRINTTYPE_L: /* non standard (L for integers) */
case PRINTTYPE_ll:
* va_arg(ap, long long*) = buf.i; break;
case PRINTTYPE_j:
* va_arg(ap, intmax_t*) = buf.i; break;
case PRINTTYPE_z:
* va_arg(ap, ssize_t*) = buf.i; break;
case PRINTTYPE_t:
* va_arg(ap, ptrdiff_t*) = buf.i; break;
//default: return -1;
}
break;
/*case '%':
printc(&buf, '%');
break;*/
default: /* invalid format: print the format itself */
//printc(&buf, '%');
fmt = fmtBegin;
case '%':
printc(&buf, '%');
break;
//return -1; /* error: invalid format */
}
}
}
return buf.i;
}
/** Functions writing a character on the buffer of a specific type. See the
** comments about the ‘F_gnprintc’ type in <printf.h> for more details.
**/
/* Function for use with ‘snprintf’: write a character on a character string, if
* the size does not exceed the maximal size.
* We should code an unsecure version of this function for use with ‘sprintf’
* (called ‘sprintChar’, and not verifying the size before writing), but since
* a ‘size_t’ integer is big enough to index any byte in the memory, this
* function with the size ‘SIZE_MAX’ will fit. */
static void snprintChar(PrintFrame* buf, wint_t c) {
if(buf->i < buf->n)
((char*)buf->p)[buf->i] = (char)c;
++buf->i;
}
/* Function for use with ‘fprintf’: write a character on a file. We do not need
* a secure version since ‘fnprintf’ does not exist, and moreover calling a
* secure version with the size ‘SIZE_MAX’ could not fit, because we might want
* to write more than ‘SIZE_MAX’ bytes into the disk (). */
static void fprintChar(PrintFrame* buf, wint_t c) {
putc(c, buf->p);
++buf->i;
}
/** At least, all the standard ‘printf’ functions.
**/
int vsnprintf(char* dest, size_t n, const char* fmt, va_list ap) {
int r = vgnprintf(snprintChar, dest, n, fmt, ap);
if((size_t)r < n)
dest[r] = '\0';
return r;
}
int vsprintf(char* dest, const char* fmt, va_list ap) {
return vsnprintf(dest, SIZE_MAX, fmt, ap);
}
int snprintf(char* dest, size_t n, const char* fmt, ...) {
va_list ap;
int r;
va_start(ap, fmt);
r = vsnprintf(dest, n, fmt, ap);
va_end(ap);
return r;
}
int sprintf(char* dest, const char* fmt, ...) {
va_list ap;
int r;
va_start(ap, fmt);
r = vsprintf(dest, fmt, ap);
va_end(ap);
return r;
}
int vfprintf(FILE* f, const char* fmt, va_list ap) {
return vgnprintf(fprintChar, f, SIZE_MAX, fmt, ap);
}
int vprintf(const char* fmt, va_list ap) {
return vfprintf(stdout, fmt, ap);
}
int fprintf(FILE* f, const char* fmt, ...) {
va_list ap;
int r;
va_start(ap, fmt);
r = vfprintf(f, fmt, ap);
va_end(ap);
return r;
}
int printf(const char* fmt, ...) {
va_list ap;
int r;
va_start(ap, fmt);
r = vprintf(fmt, ap);
va_end(ap);
return r;
}
pour tester
Pour comparer avec le comportement standard, il suffit de ne pas lier avec printf.c.
#include "printf.h"
#define test(fmt, ...) \
do { \
r = snprintf(buf, 36, fmt, ##__VA_ARGS__); \
printf("#%-2i [%02i] %10s “%s”%n", i++, r, fmt, buf, &n); \
printf("\t\t(→ %2i)\n", n); \
} while(0)
int main(void) {
char buf[1024];
int i = 0, r, n = 666;
long li = 1;
long long lli = 2;
unsigned long long llu = 3;
ptrdiff_t ti = 4;
ssize_t zi = 5;
size_t zu = 6;
short hi = 7;
unsigned char hhu = 8;
intmax_t ji = 9;
uintmax_t ju = 10;
/* ptr */
fprintf(stdout, " buf: %p\n", buf);
fprintf(stdout, " n: %p\n", &n);
/* % */
test("%%");
/* simple integer conversions, some flags (sign, alt) */
test("%d", 42);
test("%i", -42);
test("% i", 42);
test("%+i", 42);
test("%+ i", 42);
test("%+ i", -42);
test("%X", 42);
test("%#x", 42);
test("%#o", 42);
/* precision, width, padding (spaces or '0' flag), alignment ('-' flag) */
test("%.5d", 42);
test("%0+.5d", 42);
test("%0+9.5d", 42);
test("%9.5d", 42);
test("%-+9.5d", 42);
test("%.i", 42);
test("%.i", 0);
test("%+.i", 0);
test("%17.i", 0);
/* more */
test("%5d", 42);
test("%5d", -42);
test("%05d", 42);
test("%05d", -42);
test("%+05d", 42);
test("% 5d", 42);
test("% 5d", -42);
test("%-5d", 42);
test("%- 5d", 42);
test("%-5d", -42);
test("%-05d", -42);
test("%-30d", -42);
/* pad (spaces or zeros) when too large */
test("%6d", 314159);
test("%5d", 314159);
test("%5d", -314159);
test("%08d", 314159);
test("%07d", 314159);
test("%06d", 314159);
test("%05d", 314159);
/* strings (width, precision, padding, null) */
test("%s", "123456789,123456789,123456789,123456789."); /* error: not enough space for ‘snprintf’ (limited to 36 in the ‘test’ macro) */
test("%20s", "123456789.");
test("%.5s", "123456789.");
test("%20.5s", "123456789.");
test("%-20s", "123456789.");
test("%-20.5s", "123456789.");
test("%-20.15s", "123456789.");
test("%-5.15s", "123456789.");
test("%20.15s", "123456789.");
test("%5.15s", "123456789.");
test("%s", (char*)NULL);
test("%20s", (char*)NULL);
test("%.5s", (char*)NULL);
test("%20.5s", (char*)NULL);
test("%s", "");
test("% s", "");
/* types */
test("%li", li);
test("%lli", lli);
test("%llu", llu);
test("%ti", ti);
test("%zi", ti);
test("%zi", zi);
test("%zu", zu);
test("%hi", hi);
test("%hhu", hhu);
test("%ji", ji);
test("%ju", ju);
test("%ju", hhu); /* error: bad type */
/* '*' for width or precision */
test("%*u", 10, 123);
test("%.*u", 10, 123);
test("%*.*u", 10, 5, 123);
/* “n” conversions */
test("%i%n", 123, &n);
test("%i%Ln", 123, &n);
/* invalid format */
test("%lhu", 123);
test("%hlu", 123);
test("%h u", 123);
test("%h5u", 123);
test("%h.5u", 123);
test("%.5.6u", 123);
return 0;
}
Voilà mon code. J'suis pas trop satisfait, je fais beaucoup de if...else pour pas grand chose je pense. Je verrai pour améliorer ça si je suis motivé.
Tout est géré (ou partiellement) sauf %n, et tous les flottants. Le partiellement, c'est pour tout ce qui touche aux size_t, ptrdiff_t et conneries du genre. J'ai pris un type arbitraire.
#include <ctype.h>
#include <float.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#define aprintf(...) fprintf(stderr, __VA_ARGS__)
enum {
FLG_MINUS = 0x01,
FLG_PLUS = 0x02,
FLG_SPACE = 0x04,
FLG_SHARP = 0x08,
FLG_ZERO = 0x10
};
enum {
LEN_hh,
LEN_ll,
LEN_h,
LEN_l,
LEN_j,
LEN_z,
LEN_t,
LEN_L,
N_LEN
};
typedef enum {
TYP_C, /* char */
TYP_S, /* short */
TYP_I, /* int */
TYP_L, /* long */
TYP_LL, /* long long */
TYP_F, /* float */
TYP_D, /* double */
TYP_LD, /* long double */
TYP_IMAX, /* uintmax_t */
TYP_SZT, /* size_t */
TYP_PDIF, /* ptrdiff_t */
TYP_WC, /* wchar_t */
TYP_PVOID /* void* */
} type_e;
/* Type sign */
enum {
SIG_U = 0x01,
SIG_S = 0x02,
SIG_x = 0x04,
SIG_X = 0x08
};
enum {
ENDIAN_BIG,
ENDIAN_LIT
};
typedef union {
unsigned char uc;
unsigned long long ull;
unsigned short us;
unsigned long ul;
unsigned int ui;
uintmax_t uimax;
size_t szt;
ptrdiff_t pdif;
wchar_t wc;
void * pvoid;
long double ld;
double d;
float f;
} types_u;
typedef struct args_s {
unsigned flags;
int fieldWidth;
int precision;
int lenMod;
} args_t;
void initArgs(args_t * ag) {
ag->flags = 0;
ag->fieldWidth = -1;
ag->precision = -1;
ag->lenMod = -1;
}
/* Search flags after a % character */
const char * getFlags(const char * fmt, args_t * ag) {
char flags[] = "-+ #0";
int dflags[] = {
FLG_MINUS, FLG_PLUS, FLG_SPACE, FLG_SHARP, FLG_ZERO
};
const char * t;
while (1) {
t = strchr(flags, *fmt);
if (t) {
ag->flags |= dflags[t - flags];
fmt++;
}
else {
break;
}
}
/* If + and space are presents, space is ignored */
if (ag->flags & FLG_PLUS && ag->flags & FLG_SPACE) {
aprintf("'+' and ' ' flag are presents. ' ' is ignored.\n");
ag->flags ^= FLG_SPACE;
}
/* If - and 0 are present, 0 is ignored */
if (ag->flags & FLG_MINUS && ag->flags & FLG_ZERO) {
aprintf("'+' and '0' flag are presents. '0' is ignored.\n");
ag->flags ^= FLG_ZERO;
}
return fmt;
}
const char * getPrecision(const char * fmt, int * precision, va_list * ap) {
if (*fmt == '*') {
*precision = va_arg(*ap, int);
fmt++;
}
else if (isdigit(*fmt)) {
int n = 0;
do {
n *= 10;
n += (*fmt - '0');
fmt++;
} while (isdigit(*fmt));
*precision = n;
}
return fmt;
}
const char * getLenMod(const char * fmt, args_t * ag) {
/* Note: the multichar characters are placed before single bye char */
char * mods[N_LEN] = {
"hh", "ll", "h", "l", "j", "z", "t", "L"
};
int lenMods[N_LEN] = {
2, 2, 1, 1, 1, 1, 1, 1
};
int dmods[N_LEN] = {
LEN_hh, LEN_ll, LEN_h, LEN_l, LEN_j, LEN_z, LEN_t, LEN_L
};
int i;
for (i = 0; i < N_LEN; i++) {
if (strncmp(mods[i], fmt, lenMods[i]) == 0) {
ag->lenMod = dmods[i];
fmt += lenMods[i];
break;
}
}
return fmt;
}
int printIntJustify(FILE * f, char * s, int offset, args_t * ag) {
int sz = strlen(s);
int precision;
int fieldw;
int np;
int i;
if (sz - offset < ag->precision)
precision = ag->precision - sz + offset;
else
precision = 0;
if (sz + precision < ag->fieldWidth)
fieldw = ag->fieldWidth - (sz + precision);
else
fieldw = 0;
np = 0;
if (ag->flags & FLG_ZERO) {
if (ag->precision < 0 || sz + precision < ag->precision) {
for (i = 0; i < fieldw; i++) {
fputc('0', f);
np++;
}
}
else {
for (i = 0; i < fieldw; i++) {
fputc(' ', f);
np++;
}
}
}
else if ((ag->flags & FLG_MINUS) == 0) {
for (i = 0; i < fieldw; i++) {
fputc(' ', f);
np++;
}
}
for (i = 0; i < offset; i++) {
fputc(s[i], f);
np++;
}
for (i = 0; i < precision; i++) {
fputc('0', f);
np++;
}
for (i = offset; s[i]; i++) {
fputc(s[i], f);
np++;
}
if (ag->flags & FLG_MINUS) {
for (i = 0; i < fieldw; i++) {
fputc(' ', f);
np++;
}
}
return np;
}
int printStrJustify(FILE * f, char * s, wchar_t * sw, int nchar, args_t * ag) {
int sz;
int np;
int i;
if (s)
sz = strlen(s);
else
sz = wcslen(sw);
if (nchar >= 0)
sz = nchar;
np = 0;
if ((ag->flags & FLG_MINUS) == 0) {
for (i = 0; i < ag->fieldWidth - sz; i++) {
if (s)
fputc(' ', f);
else
fputwc(L' ', f);
np++;
}
}
for (i = 0; ((s && s[i]) || (sw && sw[i])) && i < sz; i++) {
if (s)
fputc(s[i], f);
else
fputwc(sw[i], f);
np++;
}
if (ag->flags & FLG_MINUS) {
for (i = np; i < ag->fieldWidth; i++) {
if (s)
fputc(' ', f);
else
fputwc(L' ', f);
np++;
}
}
return np;
}
int getEndian(void) {
unsigned n = 0x12;
char * p = (char *) &n;
if (*p == 0x12)
return ENDIAN_LIT;
else
return ENDIAN_BIG;
}
char * getDigit(char * s, unsigned long long ull, int base, char * ascii) {
if (ull >= (unsigned long long) base)
s = getDigit(s, ull / base, base, ascii);
*s = ascii[ull % base];
return s + 1;
}
int printInteger(FILE * f, types_u n, int sign, type_e type, int base, args_t * ag) {
char snb[32] = "";
char * s = snb;
unsigned long long ull;
static char * ascii = "0123456789abcdefghijklmnopqrstuvwxyz";
static char * asciiX = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int offset;
ull = 0;
if (sign == SIG_S) {
if (type == TYP_I && (int) n.ui < 0) {
ull = - (int) n.ui;
*s++ = '-';
}
else if (type == TYP_C && (char) n.uc < 0) {
ull = - (char) n.uc;
*s++ = '-';
}
else if (type == TYP_S && (short) n.us < 0) {
ull = - (short) n.us;
*s++ = '-';
}
else if (type == TYP_L && (long) n.ul < 0) {
ull = - (long) n.ul;
*s++ = '-';
}
else if (type == TYP_LL && (long long) n.ull < 0) {
ull = - (long long) n.ull;
*s++ = '-';
}
else if (type == TYP_IMAX && (intmax_t) n.uimax < 0) {
ull = - (intmax_t) n.uimax;
*s++ = '-';
}
else if (ag->flags & FLG_PLUS) {
*s++ = '+';
}
else if (ag->flags & FLG_SPACE) {
*s++ = ' ';
}
}
if (ull == 0) {
if (type == TYP_I) {
ull = n.ui;
}
else if (type == TYP_C) {
ull = n.uc;
}
else if (type == TYP_S) {
ull = n.us;
}
else if (type == TYP_L) {
ull = n.ul;
}
else if (type == TYP_LL) {
ull = n.ull;
}
else if (type == TYP_SZT) {
ull = n.szt;
}
else if (type == TYP_PVOID) {
int endian = getEndian();
/* TODO: check this out ! fixme... */
if (endian == ENDIAN_BIG) {
ull = (unsigned long long) n.pvoid;
}
else {
ull = (unsigned long long) n.pvoid;
}
}
/* Special case... */
else if (type == TYP_PDIF) {
if (ag->flags & FLG_PLUS)
*s++ = '+';
else if (ag->flags & FLG_SPACE)
*s++ = ' ';
ull = n.pdif;
}
}
if ((ull != 0 && (ag->flags & FLG_SHARP)) || type == TYP_PVOID) {
*s++ = '0';
if (base == 16) {
if (sign & SIG_x)
*s++ = 'x';
else
*s++ = 'X';
}
}
if (ag->precision == 0 && ull == 0 && (ag->flags & FLG_SHARP) == 0) {
return 0;
}
offset = s - snb;
if (sign & SIG_x)
getDigit(s, ull, base, ascii);
else
getDigit(s, ull, base, asciiX);
return printIntJustify(f, snb, offset, ag);
}
int treatInteger(FILE * f, const char ** fmt, args_t * ag, va_list * ap) {
const char * s = *fmt;
types_u nb;
type_e type;
int sign;
int base;
int n;
if (*s == 'd' || *s == 'i') {
sign = SIG_S;
base = 10;
}
else if (*s == 'u') {
sign = SIG_U;
base = 10;
}
else if (*s == 'o') {
sign = SIG_U;
base = 8;
}
else if (*s == 'x' || *s == 'X' || *s == 'p') {
sign = SIG_U;
if (*s == 'x' || *s == 'p')
sign |= SIG_x;
else
sign |= SIG_X;
base = 16;
}
else {
aprintf("Wrong formater 'treatInteger'.\n");
return 0;
}
if (*s == 'p') {
nb.pvoid = va_arg(*ap, void *);
type = TYP_PVOID;
}
else if (ag->lenMod < 0) {
nb.ui = va_arg(*ap, unsigned int);
type = TYP_I;
}
else if (ag->lenMod == LEN_hh) {
nb.uc = (unsigned char) va_arg(*ap, unsigned int);
type = TYP_C;
}
else if (ag->lenMod == LEN_ll) {
nb.ull = va_arg(*ap, unsigned long long);
type = TYP_LL;
}
else if (ag->lenMod == LEN_h) {
nb.us = (unsigned short) va_arg(*ap, unsigned int);
type = TYP_S;
}
else if (ag->lenMod == LEN_l) {
nb.ul = va_arg(*ap, unsigned long);
type = TYP_L;
}
else if (ag->lenMod == LEN_j) {
nb.uimax = va_arg(*ap, uintmax_t);
type = TYP_IMAX;
}
else if (ag->lenMod == LEN_z) {
nb.szt = va_arg(*ap, size_t);
type = TYP_SZT;
}
else if (ag->lenMod == LEN_t) {
nb.pdif = va_arg(*ap, ptrdiff_t);
sign = SIG_U;
type = TYP_PDIF;
}
else {
aprintf("Wrong formater %c.\n", **fmt);
return 0;
}
n = printInteger(f, nb,sign, type, base, ag);
*fmt = s + 1;
return n;
}
/* Return the number of char written */
int treatArg(FILE * f, const char ** fmt, args_t * ag, va_list * ap) {
const char * s = *fmt;
int n = 0;
switch (*s) {
case 'o': case 'u': case 'x': case 'X':
if (ag->flags & FLG_PLUS) {
aprintf("'+' flags with '%c' formater. '+' ignored.\n", *s);
ag->flags ^= FLG_PLUS;
}
if (ag->flags & FLG_SPACE) {
aprintf("' ' flags with '%c' formater. ' ' ignored.\n", *s);
ag->flags ^= FLG_SPACE;
}
n = treatInteger(f, &s, ag, ap);
break;
case 'd': case 'i':
if (ag->flags & FLG_SHARP) {
aprintf("'#' flags with '%c' formater. '+' ignored.\n", *s);
ag->flags ^= FLG_SHARP;
}
n = treatInteger(f, &s, ag, ap);
break;
case 'c':
s++;
if (ag->flags & (FLG_PLUS | FLG_SHARP | FLG_SPACE | FLG_ZERO)) {
aprintf("Only '-' flag is accepted with 'c' formater.\n");
return 0;
}
if (ag->precision >= 0) {
aprintf("Precision used with 'c' formater.\n");
return 0;
}
if (ag->lenMod == LEN_L) {
wchar_t wstmp[2] = L"";
wstmp[0] = (wchar_t) va_arg(*ap, unsigned int);
n = printStrJustify(f, NULL, wstmp, -1, ag);
}
else {
char stmp[2] = "";
stmp[0] = (unsigned char) va_arg(*ap, unsigned int);
n = printStrJustify(f, stmp, NULL, -1, ag);
}
break;
case 's': {
char * stmp;
wchar_t * wstmp;
int nchar;
s++;
if (ag->flags & (FLG_PLUS | FLG_SHARP | FLG_SPACE | FLG_ZERO)) {
aprintf("Only '-' flag is accepted with 'c' formater.\n");
return 0;
}
if (ag->lenMod == LEN_l) {
wstmp = va_arg(*ap, wchar_t *);
stmp = NULL;
}
else {
stmp = va_arg(*ap, char *);
wstmp = NULL;
}
nchar = -1;
if (ag->precision >= 0) {
int sz;
if (stmp)
sz = strlen(stmp);
else
sz = wcslen(wstmp);
if (sz > ag->precision) {
nchar = ag->precision;
}
}
n = printStrJustify(f, stmp, wstmp, nchar, ag);
break;
}
case 'p':
if (ag->flags & (FLG_PLUS | FLG_SHARP | FLG_SPACE | FLG_ZERO)) {
aprintf("Only '-' flag is accepted with 'p' formater.\n");
return 0;
}
if (ag->precision >= 0) {
aprintf("Precision used with 'p' formater.\n");
return 0;
}
n = treatInteger(f, &s, ag, ap);
break;
default:
break;
}
*fmt = s;
return n;
}
int printArg(FILE * f, const char ** fmt, va_list * ap) {
const char * s = *fmt;
args_t ag;
int n;
if (**fmt == '%') {
if (fputc('%', f) != EOF)
return 1;
else
return 0;
}
initArgs(&ag);
s = getFlags(s, &ag);
s = getPrecision(s, &ag.fieldWidth, ap);
if (*s == '.') {
s++;
s = getPrecision(s, &ag.precision, ap);
}
s = getLenMod(s, &ag);
n = treatArg(f, &s, &ag, ap);
*fmt = s;
return n;
}
int zvfprintf(FILE * f, const char * fmt, va_list ap) {
int n;
for (n = 0; *fmt != '\0'; ) {
switch (*fmt) {
case '%':
fmt++;
n += printArg(f, &fmt, &ap);
break;
default:
fputc(*fmt, f);
fmt++;
n++;
break;
}
}
return n;
}
int zvprintf(const char * fmt, va_list ap) {
return zvfprintf(stdout, fmt, ap);
}
int zfprintf(FILE * f, const char * fmt, ...) {
va_list ap;
int n;
va_start(ap, fmt);
n = zvfprintf(f, fmt, ap);
va_end(ap);
return n;
}
int zprintf(const char * fmt, ...) {
va_list ap;
int n;
va_start(ap, fmt);
n = zvfprintf(stdout, fmt, ap);
va_end(ap);
return n;
}
#define mprintf(...) { printf(": [%d]\n", printf(__VA_ARGS__)); \
printf(": [%d]\n", zprintf(__VA_ARGS__)); }
int main(void) {
unsigned int n = 0x1000;
mprintf("%20p", &n);
mprintf("%#.0x [%c] [%-3.1s]", 0, 'e', "salut");
mprintf ("Characters: %c %c ", 'a', 65);
mprintf ("Decimals: %d %ld", 1977, 650000L);
mprintf ("Preceding with blanks: %10d ", 1977);
mprintf ("Preceding with zeros: %010d ", 1977);
mprintf ("Some different radixes: %d %x %o %#x %#o ", 100, 100, 100, 100, 100);
mprintf ("floats: %4.2f %+.0e %E ", 3.1416, 3.1416, 3.1416);
mprintf ("Width trick: %*d ", 5, 10);
mprintf ("%s ", "A string");
static wchar_t wstr[] = L"abcdefghijk";
mprintf("|1234567890123|");
mprintf("|%13ls|", wstr);
mprintf("|%-13.9ls|", wstr);
mprintf("|%13.10ls|", wstr);
mprintf("|%13.11ls|", wstr);
mprintf("|%13.15ls|", &wstr[2]);
mprintf("|%13lc|", (wint_t) wstr[5]);
fflush(stdout);
return EXIT_SUCCESS;
}
Dans certains cas, j'ai mit des messages d'erreurs, mais je l'ai pas fait partout, ça alourdit énormément le code.
Je peux me permettre ?
C'est histoire que j'avais vu le défis quand il a été poster et je voulais vraiment le faire, mais j'ai pas eu assez de temps (exam et truck du genre...) Et aussi le titre n'est pas préfixer avec [FAIT].
En plus c'est une participation basique pour le niveau 2 uniquement.
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
static int size(char *str)
{
int i;
for(i = 0; str[i] != '\0'; i++);
return i;
}
static char* convert(int n, int base)
{
const char *alpha = "0123456789ABCDEF";
char *s = NULL;
char *_s = NULL;
int digit = 0, i = 0,j,k = 0, tmp = n;
while(tmp > 0)
{
digit++;
tmp /= base;
}
s = malloc(digit * sizeof(char));
_s = malloc(digit * sizeof(char));
if(!s || !_s) exit(EXIT_FAILURE);
while(n > 0)
{
s[i] = alpha[n % base];
n /= base;
i++;
}
for(j = i-1; j>=0; j--, k++)
_s[k] = s[j];
free(s), s = NULL;
return _s;
}
static void __putchar(char c)
{
fwrite(&c, sizeof(char),1,stdout);
}
static int eval(char *str, va_list ap)
{
int i,x,ret = 0, siz, base = 10;
char *s = NULL;
for(i = 0; str[i] != '\0'; i++)
{
if(str[i] == '%')
{
i++;
switch(str[i])
{
case '%':
__putchar('%');
ret++;
break;
case 'd':
case 'i':
base = 10;
x = va_arg(ap, int);
if(x < 0)
{
x = -x;
__putchar('-');
ret++;
}
s = convert(x, base);
siz = size(s);
for(; *s != '\0'; s++)
{
__putchar(*s);
ret++;
}
s -= siz;
free(s), s = NULL;
break;
case 'x':
base = 18;
case 'o':
base -= 2;
x = va_arg(ap, int);
if(x < 0)
{
x = -x;
__putchar('-');
ret++;
}
s = convert(x, base);
siz = size(s);
for(; *s != '\0'; s++)
{
__putchar(*s);
ret++;
}
s -= siz;
free(s), s = NULL;
break;
case 'c':
__putchar(va_arg(ap, int));
ret++;
break;
case 's':
s = va_arg(ap, char*);
for(; *s != '\0'; s++)
{
__putchar(*s);
ret++;
}
break;
}
}
else
{
__putchar(str[i]);
ret++;
}
}
return ret;
}
static int zprintf(char *str, ...)
{
int ret;
va_list ap;
va_start(ap, str);
ret = eval(str,ap);
va_end(ap);
return ret;
}
int main(void)
{
zprintf("Print : %% %o %x %d %c %s \n",0xA,0xA,0xA, 'c', "Salut");
return 0;
}
Chaud l'indentation de 1 espace... 2 à la rigueur, mais là c'est pas très lisible.
J'ai pas regardé en détail ni testé mais le malloc ça le fait mal dans un printf.
Bah printf est une fonction qu'on utilise souvent, si elle commence à faire des allocations mémoires, ça peut devenir problématique (d'ailleurs, tu ne pourrais pas supprimer une des deux allocations ? Il me semblait avoir vu des codes n'utilisant qu'un seul tableau. Mais bon, à confirmer)... AMHA tu devrais le faire en statique et fixer une taille plafond. Mais bon, c'est pas essentiel non plus. Par ailleurs, on dirait que tu as réutilisé ma vieille fonction __putchar. Fais gaffe, elle est boguée (cf. remarque de Taurre première page).
Du coup pour éviter le malloc, on doit passer par des fonctions secondaires qui font l'affichage, ou bien tout simplement le faire en statique. Pour les deux allocations je pense que c'est possible d'en laisser qu'une seule,mais tu sais la flemme.
Et pour la fonction __putchar, j'ai pas vu la remarque de Taurre ( ni ta fonction d'ailleur ), j'essayerai de faire une autre.
Attention qu'avec cette fonction tu écris le byte de poids faible de la variable c, tu écriras donc probablement 0 sur une machine big endian. De plus, le retour de putchar doit être de type int (retourne EOF en cas d'erreur) et non size_t.
EDIT : Aussi, les noms d'identificateurs commençant par deux underscores sont réservés et ne doivent par conséquent pas être utilisés
Après un long temps et beaucoup d'efforts, voici une nouvelle version de mon code. Je mets le lien GitHub pour ne pas gêner la lecture du topic car le code est long. Merci d'avance à tous les courageux qui reliront.
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.