@informaticienzero : Pour setjmp et stdarg, ça me semble également difficile. stdlib ça me semble être possible entièrement (à moins que j'oublie quelque chose).
Pour setjmp, c'est juste sauvegarder un état à un moment donné, il suffit d'utiliser une petite routine en assembleur qui sauvegarde les registres (setjmp) et une autre qui les restaure (longjmp). Pour stdlib.h, tu as des fonctions comme system, exit, abort ou les fonctions liées aux caractères étendus qui peuvent être gênantes.
Exact, j'ai édité avant que tu postes pour stdlib. Pour setjmp, c'est aussi dépendant de l'implémentation (par définition, avec l'ASM c'est difficile de faire l'unanimité).
Exact, j'ai édité avant que tu postes pour stdlib. Pour setjmp, c'est aussi dépendant de l'implémentation (par définition, avec l'ASM c'est difficile de faire l'unanimité).
Certes, mais là ça ne dépend plus que du processeur et non du système d'exploitation. La seule difficulté qui pourrait y avoir, c'est entre un processeur 32 bits et 64 bits pour l'empilage des arguments.
Exact, j'ai édité avant que tu postes pour stdlib. Pour setjmp, c'est aussi dépendant de l'implémentation (par définition, avec l'ASM c'est difficile de faire l'unanimité).
Certes, mais là ça ne dépend plus que du processeur et non du système d'exploitation. La seule difficulté qui pourrait y avoir, c'est entre un processeur 32 bits et 64 bits pour l'empilage des arguments.
Bah c'est ça aussi l'implémentation. De toute manière, la non-conformité de l'OS est souvent due à des détails du hardware.
Voila une partie du code de printf sur laquelle j'attends énormément de remarque. Il est loin d'être parfait, parfois brouillon, donc je suis preneur de tout (tant que vous n'en venez pas au insulte !).
Le code compile avec la ligne suivante (quelques soit la version normalement) :
gcc -Wall -ansi -pedantic -c printf.c -o printf.o
Actuellement le code gère les formats :
"%s" (avec l), avec les fonctions avancés (peut être pas toute)
"%c" (avec l)
"%n"
"%d %i" (avec h, l, ll), avec les fonctions avancés
"%u", même chose que "%d" et "%i"
"%p", à revoir certainement...
"%x %X %o" avec les mêmes choses que "%u" ET la gestion de '#'
"%f %lf %e %E %g %G", en cours, avec les options avancés, en béta, sujets à modifications
Je n'ai pas eu le temps de faire énormément de test, il fonctionne sur des formats "corrects" (à voir s'il plante sur des formats incorrects, si c'est le cas merci de me le dire !).
Le code gère les affichages complexes pour les entiers : %03i, %-3d, %+03d, etc... J'ai essayé d'être le plus modulable possible pour pouvoir facilement réutiliser le code pour les formats u, x, et o (ça devrait être très rapide avec ma base actuelle).
#include "printf.h"
#include <stdarg.h>
#include <stdio.h> /* Only for putchar */
#include <wchar.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <float.h>
#include <math.h>
#if __STDC_VERSION__ >= 199901L
#include <stdint.h>
#define VP_C99__
typedef intmax_t vp_intmax_t ;
typedef uintmax_t vp_uintmax_t ;
#else
typedef long int vp_intmax_t ;
typedef unsigned long int vp_uintmax_t ;
#endif
enum flag_t {
FLAG_LJUST = 1 << 0,
FLAG_PSIGN = 1 << 1,
FLAG_SPACE = 1 << 2,
FLAG_SHARP = 1 << 3,
FLAG_ZEROS = 1 << 4
};
#define HAS_LEFT_JUSTIFIED__(c) ((c) & FLAG_LJUST)
#define HAS_ZERO_PADDED__(c) ((c) & FLAG_ZEROS)
#define HAS_PRINT_SIGN__(c) ((c) & FLAG_PSIGN)
#define HAS_BLANK_IF_POS__(c) ((c) & FLAG_SPACE)
#define HAS_SHARP_FLAG__(c) ((c) & FLAG_SHARP)
/* Check if the specified character is a "precision" character. i.e, it gives information on paramater size (long, short, long long). */
static int is_spe_ (char c) {
return c == 'l'
|| c == 'h' ;
}
/* Check if the specified character is a "format" character. i.e, it gives information about the type of parameter and the conversion to do. */
static int is_format_ (char c) {
return c == 's'
|| c == 'i'
|| c == 'x'
|| c == 'u'
|| c == 'd'
|| c == 'c'
|| c == 'u'
|| c == 'x'
|| c == 'X'
|| c == 'o'
|| c == 'p'
|| c == 'f'
|| c == 'e'
|| c == 'E'
|| c == 'g'
|| c == 'G'
|| c == 'n';
}
static enum flag_t get_flags_ (const char **format) {
enum flag_t flags = 0 ;
for (; ; (*format)++) {
switch (**format) {
case '-': flags |= FLAG_LJUST ; break ;
case '0': flags |= FLAG_ZEROS ; break ;
case '+': flags |= FLAG_PSIGN ; break ;
case ' ': flags |= FLAG_SPACE ; break ;
case '#': flags |= FLAG_SHARP ; break ;
default:
goto end ;
}
}
end:
return flags ;
}
/* Get information from format, and store it into the specified arguments :
flags is set to a combination of allowed FLAG (see above), if there is flag in format
min_width is set to the minimum width the representation may be, if there is one specified
max_width is set to the maximum width the representation may be, if there is one specified
args is used in case of the minimum width must be taken from a parameter
All parameters, except format and args, may be NULL. In this cas, they are not set. */
static void get_format_parts_ (const char *format,
enum flag_t *flags,
int *min_width,
int *max_width,
va_list *args) {
int tmp ;
char *aux ;
enum flag_t f ;
/* Set flags value, if it's not NULL. */
f = get_flags_ (&format) ;
if (flags != NULL) *flags = f;
/* Set min_width value. If format contains '*', min_width is taken from arg_list, otherwize it's taken from format. */
tmp = -1 ;
if (*format != '*') {
tmp = strtol(format, &aux, 10) ;
if (aux == format) {
tmp = -1 ;
}
format = aux ;
}
else {
tmp = va_arg(*args, unsigned int) ;
}
if (min_width != NULL) *min_width = tmp ;
/* Set max_width, if it's 0, just set it to UINT_MAX. */
tmp = -1 ;
if (*format == '.') {
++format ;
if (*format != '*') {
tmp = strtol(format, &aux, 10) ;
}
else {
tmp = va_arg(*args, unsigned int) ;
}
}
if (max_width != NULL) *max_width = tmp ;
}
/* Get an vp_intmax_t from va_list after converting it according to spe.
spe must be h (short), l (long int), L (vp_intmax_t) or any other character (int)
args must not be NULL */
static vp_intmax_t get_lli_ (char spe, va_list *args) {
switch (spe) {
case 'h':
return (vp_intmax_t)va_arg(*args, int) ;
break ;
case 'l':
return (vp_intmax_t)va_arg(*args, long int) ;
break ;
#ifdef VP_C99__
case 'L':
return (vp_intmax_t)va_arg(*args, vp_intmax_t) ;
break ;
#endif
default:
return (vp_intmax_t) va_arg(*args, int) ;
}
}
/* Get an vp_uintmax_t from va_list after converting it according to spe.
spe must be h (unsigned short), l (unsigned long int), L (vp_uintmax_t) or any other character (unsigned int)
args must not be NULL */
static vp_uintmax_t get_ulli_ (char spe, va_list *args) {
switch (spe) {
case 'h':
return (vp_uintmax_t)va_arg(*args, unsigned int) ;
break ;
case 'l':
return (vp_uintmax_t)va_arg(*args, unsigned long int) ;
break ;
#ifdef VP_C99__
case 'L':
return (vp_uintmax_t)va_arg(*args, vp_uintmax_t) ;
break ;
#endif
default:
return (vp_uintmax_t) va_arg(*args, unsigned int) ;
}
}
/* Return the number of character needed to print var in base base. */
static int ullinchar_ (vp_uintmax_t var, int base) {
int count = 0 ;
if (var == 0) {
return 1 ;
}
for (; var != 0; var /= base) {
count ++ ;
}
return count ;
}
/* Convert the vp_uintmax_t val into a string, stored in the array pointed by buff. The conversion is made
according to base and upper. If nchar is not NULL, it contains the number of character used to represend val.
The char array is NOT nul terminated. */
static int ulli2str_ (vp_uintmax_t val,
char *buff,
int base,
int upper,
int *nchar)
{
static const char *hexxdigits = "0123456789abcdefghijklmnopqrstuvwxyz" ;
int i ;
int ntchar ;
base = (base == 0) ? 10 : base ;
ntchar = ullinchar_ (val, base) ;
for (i=ntchar-1; i >= 0; i--, val /= base) {
buff[i] = hexxdigits[val%base] ;
if (upper) {
buff[i] = toupper(buff[i]) ;
}
}
if (nchar != NULL) {
*nchar = ntchar ;
}
return ntchar ;
}
/* Print everything needed before a number according to specification :
neg if the number is negative
padd the minimum_width
sign if the sign must be printed, even with positive value
blank if a blank character must be printed before positive value
left_justified
nchar the number of char used to represent the number */
static int vp_before_number_ (int neg,
int padd,
int sign,
int blank,
int zero_padded,
int left_justified,
int nchar)
{
int count = 0 ;
char padchar = (zero_padded) ? '0' : ' ' ;
if (left_justified || zero_padded) {
if (neg) {
putchar('-') ;
++ count ;
}
else if (sign) {
putchar('+') ;
++ count ;
}
else if (blank && !zero_padded) {
putchar(' ') ;
++ count ;
}
}
if (!left_justified) {
count += ((sign || blank) && !zero_padded) ? 1 : 0 ;
for (; count + nchar < padd; ++count) {
putchar(padchar) ;
}
if (sign && !zero_padded) {
putchar('+') ;
}
else if (blank && !zero_padded) {
putchar(' ') ;
}
}
return count ;
}
static int vp_after_number_ (int padd,
int nchar_printed,
int left_justified) {
int count = 0 ;
if (left_justified) {
for (; count + nchar_printed < padd; ++count) {
putchar(' ') ;
}
}
return count ;
}
#define BUFF_INT_REPR_SIZE__ 200
/* Print a formated string corresponding to val according to the specified parameters :
padd : the representation of val will be at least of padd character
sign : if not 0, a '+' character will be put before positive value
blank : if not 0 and sign not 1, a blank character will be put before positive value
zero_padded : if not 0, '0' character will be used instead of blank character to do padding
left_justified
base : the base conversion to for the conversion
upper : if specified and base >= 10, the letters will be output upper instead of lower */
static int vp_format_ulli_ (vp_uintmax_t val,
int padd,
int sign,
int blank,
int zero_padded,
int left_justified,
int base,
int upper)
{
char buff[BUFF_INT_REPR_SIZE__] ;
int ncharprint, count = 0;
int i ;
ulli2str_ (val, buff, base, upper, &ncharprint) ;
count += vp_before_number_ (0, padd, sign, blank, zero_padded, left_justified, ncharprint) ;
for (i=0; i<ncharprint; ++i, ++count) {
putchar(buff[i]) ;
}
count += vp_after_number_ (padd, count, left_justified) ;
return count ;
}
/* Print a formated string corresponding to val according to the specified parameters (base conversion is 10) :
padd : the representation of val will be at least of padd character
sign : if not 0, a '+' character will be put before positive value
blank : if not 0 and sign not 1, a blank character will be put before positive value
zero_padded : if not 0, '0' character will be used instead of blank character to do padding
left_justified */
static int vp_format_lli_ (vp_intmax_t val,
int padd,
int sign,
int blank,
int zero_padded,
int left_justified)
{
int count = 0;
vp_uintmax_t abs = (val < 0) ? -val : val ;
if (val < 0 && (left_justified || zero_padded)) {
putchar('-') ;
count ++ ;
padd -- ;
sign = 0 ;
blank = 0 ;
}
count += vp_format_ulli_ (abs, padd, sign, blank, zero_padded, left_justified, 10, 0) ;
return count ;
}
/* Print a formated string according to the format contains in 'format', the value is taken from args accord to spe specification as a signed integer. */
static int vp_li_ (const char *format, va_list *args, char spe) {
vp_intmax_t val ;
int padd ;
enum flag_t flags ;
get_format_parts_ (format, &flags, &padd, NULL, args) ;
padd = (padd < 0) ? 0 : padd ;
val = get_lli_ (spe, args) ;
return vp_format_lli_ (val, padd, HAS_PRINT_SIGN__ (flags), HAS_BLANK_IF_POS__ (flags), HAS_ZERO_PADDED__ (flags), HAS_LEFT_JUSTIFIED__ (flags)) ;
}
/* Print a formated string according to the format contains in 'format', the value is taken from args accord to spe specification as un unsigned integer. */
static int vp_ulli_ (const char *format, va_list *args, char spe) {
vp_uintmax_t val ;
int padd ;
enum flag_t flags ;
get_format_parts_ (format, &flags, &padd, NULL, args) ;
padd = (padd < 0) ? 0 : padd ;
val = get_ulli_ (spe, args) ;
return vp_format_ulli_ (val, padd, HAS_PRINT_SIGN__ (flags), HAS_BLANK_IF_POS__ (flags), HAS_ZERO_PADDED__ (flags), HAS_LEFT_JUSTIFIED__ (flags), 10, 0) ;
}
/* Print a formated string according to the format contains in 'format', the value is taken from args accord to spe specification as un unsigned integer.
Upper paramater just specified if letter must be set upper (1) or lower (0). */
static int vp_ullx_ (const char *format, va_list *args, char spe, char upper) {
vp_uintmax_t val ;
enum flag_t flags ;
int padd;
get_format_parts_ (format, &flags, &padd, NULL, args) ;
padd = (padd < 0) ? 0 : padd ;
val = get_ulli_ (spe, args) ;
if (HAS_SHARP_FLAG__ (flags) && val != 0) {
padd -= 2 ;
putchar('0') ;
putchar((upper) ? 'X' : 'x') ;
}
return vp_format_ulli_ (val, padd, HAS_PRINT_SIGN__ (flags), HAS_BLANK_IF_POS__ (flags), HAS_ZERO_PADDED__ (flags), HAS_LEFT_JUSTIFIED__ (flags), 16, upper) ;
}
#define CHAR_FOR_PTR__ 8
/* Print a formated string according to the format contains in 'format', the value is taken from args accord to spe specification and converted as un unsigned int.
Only '-' flags is allowed in format, and the only value used from format is min_width (see above). The conversion is done with no zero padded, with at least a
minimum width of CHAR_FOR_PTR, and in uppercase letter. */
static int vp_ptr_ (const char *format, va_list *args, char spe) {
vp_uintmax_t val = (unsigned int)va_arg(*args, void*) ;
int padd ;
enum flag_t flags ;
int count = 0 ;
get_format_parts_ (format, &flags, &padd, NULL, args) ;
padd = (padd < 0) ? 0 : padd ;
if (HAS_LEFT_JUSTIFIED__(flags)) {
count = vp_format_ulli_ (val, CHAR_FOR_PTR__, 0, 0, 1, 0, 16, 1) ;
for (; count < padd; ++count) {
putchar(' ') ;
}
}
else {
count = CHAR_FOR_PTR__ ;
for (; count < padd; ++count) {
putchar(' ') ;
}
vp_format_ulli_ (val, CHAR_FOR_PTR__, 0, 0, 1, 0, 16, 1) ;
}
return count ;
}
/* Print a formated string according to the format contains in 'format', the value is taken from args accord to spe specification as un unsigned integer. */
static int vp_ullo_ (const char *format, va_list *args, char spe) {
vp_uintmax_t val ;
enum flag_t flags;
int padd;
get_format_parts_ (format, &flags, &padd, NULL, args) ;
padd = (padd < 0) ? 0 : padd ;
if (HAS_SHARP_FLAG__ (flags)) {
putchar('0') ;
padd -- ;
}
val = get_ulli_ (spe, args) ;
return vp_format_ulli_ (val, padd, HAS_PRINT_SIGN__ (flags), HAS_BLANK_IF_POS__ (flags), HAS_ZERO_PADDED__ (flags), HAS_LEFT_JUSTIFIED__ (flags), 8, 0);
}
/* Print the wide string pointed by str according to specified parameters :
max_width specify the maximum number of character from str that must be printed, this value must be lower than str length
padd specify the minimum amount of character that must be printed, if it is more than str length or max_width, blank character are added
left_justified specify if string must be left justified */
static int vp_format_wstr_ (const wchar_t *str,
int max_width,
int padd,
int left_justified) {
int count = 0 ;
if (left_justified) {
for (; count < max_width && *str != '\0'; ++count, ++str) {
putwchar(*str) ;
}
}
else {
count = wcslen(str) ;
count = (count > max_width) ? max_width : count ;
}
for (; count < padd ; ++count) {
putchar(' ') ;
}
if (!left_justified) {
for (; max_width > 0 && *str != '\0'; ++str, --max_width) {
putwchar(*str) ;
}
}
return count ;
}
/* Print the string pointed by str according to specified parameters :
max_width specify the maximum number of character from str that must be printed, this value must be lower than str length
padd specify the minimum amount of character that must be printed, if it is more than str length or max_width, blank character are added
left_justified specify if string must be left justified */
static int vp_format_str_ (const char *str,
int max_width,
int padd,
int left_justified) {
int count = 0 ;
if (left_justified) {
for (; count < max_width && *str != '\0'; ++count, ++str) {
putchar(*str) ;
}
}
else {
count = strlen(str) ;
count = (count > max_width) ? max_width : count ;
}
for (; count < padd ; ++count) {
putchar(' ') ;
}
if (!left_justified) {
for (; max_width > 0 && *str != '\0'; ++str, --max_width) {
putchar(*str) ;
}
}
return count ;
}
/* Print the string (or wide string, specified by spe) contains in args, according to format. Only '-' flags is allowed. */
static int vp_str_ (const char *format, va_list *args, char spe) {
char *str ;
wchar_t *wstr ;
int max_width, min_width ;
enum flag_t flags ;
get_format_parts_ (format, &flags, &min_width, &max_width, args) ;
min_width = (min_width < 0) ? 0 : min_width ;
max_width = (max_width < 0) ? UINT_MAX : max_width ;
if (spe == 'l') {
wstr = va_arg(*args, wchar_t*) ;
return vp_format_wstr_ (wstr, max_width, min_width, HAS_LEFT_JUSTIFIED__(flags)) ;
}
else {
str = va_arg(*args, char *) ;
return vp_format_str_ (str, max_width, min_width, HAS_LEFT_JUSTIFIED__(flags)) ;
}
return 0 ;
}
/* Simple output for string with no format ! */
static int vp_simple_str_ (const char *str) {
int count = 0 ;
for (; *str != '\0'; ++count, ++str) {
putchar(*str) ;
}
return count ;
}
/* Print the char (or whar_t, specified by spe) contains in args. */
static int vp_char_ (const char *format, va_list *args, char spe) {
if (spe == 'l') {
putwchar(va_arg(*args, int)) ;
return sizeof(wchar_t) ;
}
else {
putchar(va_arg(*args, int)) ;
return 1 ;
}
}
/* Get the number of character needed to represent the integer part of a double. */
static int dbl_intp_nchar_ (double val) {
int count = 0 ;
if (val < 1) {
return 1 ;
}
for (val = floor(val); val > DBL_MIN; val = floor(val/10)) {
count ++ ;
}
return count;
}
#ifndef VP_C99__
double round (double x) {
double r = floor(x) + 0.5;
return (r < x) ? ceil(x) : r ;
}
#endif
/* Convert the double val into a string, stored in the array pointed by buff. val must be positive The conversion is made
according to base and upper. If nchar is not NULL, it contains the number of character used to represend val.
The char array is NOT nul terminated. */
static int dbl2strf_ (double val,
char *buff,
int always_point,
int precision,
int *nchar)
{
static const char *digits = "0123456789" ;
int i, count = 0 ;
int nchar_intp = dbl_intp_nchar_ (val) ;
int nchar_total ;
for (i=0; i<precision; ++i) {
val *= 10.0 ;
}
val = round(val) ;
/* nchar = number of char for integer part (from dbl_intp_nchar_) + one for point + precision. */
nchar_total = nchar_intp + precision + ((always_point || precision > 0) ? 1 : 0);
for (i = nchar_total - 1; i > nchar_intp; --i, val = floor(val/10), ++count) {
if (i > DBL_DIG) {
buff[i] = '0' ;
}
else {
buff[i] = digits[(int)fmod(val, 10)] ;
}
}
if (always_point || precision > 0) {
buff[i--] = '.' ;
++ count ;
}
for (; i >= 0; --i, val = floor(val/10), ++count) {
if (i > DBL_DIG) {
buff[i] = '0' ;
}
else {
buff[i] = digits[(int)fmod(val, 10)] ;
}
}
if (nchar != NULL) {
*nchar = count ;
}
return count ;
}
#define BUFF_DOUBLE_REPR_SIZE__ BUFF_INT_REPR_SIZE__
static int vp_double_f_ (double val,
char flags,
int min_width,
int precision) {
char buff[BUFF_DOUBLE_REPR_SIZE__] ;
int neg=0, nbchar_print, i ;
int count = 0 ;
if (val < 0) {
val = -val ;
neg = 1 ;
}
nbchar_print = dbl2strf_ (val, buff, HAS_SHARP_FLAG__ (flags), precision, NULL) ;
count += vp_before_number_ (neg, min_width, HAS_PRINT_SIGN__ (flags), HAS_BLANK_IF_POS__ (flags),
HAS_ZERO_PADDED__ (flags), HAS_LEFT_JUSTIFIED__ (flags), nbchar_print) ;
for (i=0; i<nbchar_print; ++i, ++count) {
putchar(buff[i]) ;
}
count += vp_after_number_ (min_width, count, HAS_LEFT_JUSTIFIED__ (flags)) ;
return count ;
}
#define DBL_MIN_EXP_DIG__ 3
/* Transforme a double representation as f format (2005465.0) in an exponent representation WITHOUT exponent (2.005465),
the exponent value is stored in exp args and the number of char necessary is returned.
val is the value represented in buff
nbchar is the number of character used to represent val in buff
precision is the precision required (i.e the number of character after point)
always_point must be true if a point should always be printed (event for 2.e+001 for example)
start_buff will point to the beginning of the buffer, it is possible that this function modified it */
static int dbl2stre_ (double val,
char *buff,
int always_point,
int precision,
int *nchar,
int *exp) {
*exp = 0 ;
for (; val > 10.0; val /= 10.0, ++(*exp)) { }
for (; val < 1.0; val *= 10.0, --(*exp)) { }
return dbl2strf_ (val, buff, always_point, precision, nchar) ;
}
#define DBL_PRECI_MAX__ DBL_DIG
static int vp_double_e_ (double val,
enum flag_t flags,
int min_width,
int precision,
int upper) {
char buff[BUFF_DOUBLE_REPR_SIZE__] ;
int neg=0, nbchar, count ;
int i ;
int exp ;
if (val < 0) {
val = -val ;
neg = 1 ;
}
nbchar = dbl2stre_ (val, buff, HAS_SHARP_FLAG__ (flags), precision, NULL, &exp) ;
/* Put the e and sign : */
buff[nbchar++] = (upper) ? 'E' : 'e' ;
/* Nom, we print ! */
count = 0 ;
count += vp_before_number_ (neg, min_width, HAS_PRINT_SIGN__ (flags), HAS_BLANK_IF_POS__ (flags),
HAS_ZERO_PADDED__ (flags), HAS_LEFT_JUSTIFIED__ (flags), nbchar + DBL_MIN_EXP_DIG__ + 1) ;
for (i=0; i<nbchar; ++i, ++count) {
putchar(buff[i]) ;
}
/* Juste use the unsigned long long function to put the exponent. */
count += vp_format_lli_ (exp, DBL_MIN_EXP_DIG__ + 1, 1, 0, 1, 0) ;
count += vp_after_number_ (min_width, count, HAS_LEFT_JUSTIFIED__ (flags)) ;
return count ;
}
#define DBL_MIN_REP_G__ 1e-3
#define DBL_MAX_REP_G__ 1e3
static int vp_double_g_ (double val,
enum flag_t flags,
int min_width,
int precision,
int upper) {
if (val < DBL_MAX_REP_G__ && val > DBL_MIN_REP_G__) {
if (precision > 0) {
if (DBL_EPSILON > abs(val - ceil(val))) {
precision = 0 ;
}
}
return vp_double_f_ (val, flags, min_width, precision) ;
}
else {
return vp_double_e_ (val, flags, min_width, precision, upper) ;
}
}
#ifndef VP_C99__
static int isnan(double x) {
return x != x ;
}
static int isfinite(double x) {
return !((x == x) && (x != 0) && (x + 1 == x));
}
#endif
#define NAN_STR__ "NaN"
#define INF_STR__ "Inf"
static int vp_double_nan_or_inf_ (const char *str,
enum flag_t flags,
int min_width) {
int count ;
count = vp_before_number_ (0, min_width, 0, 0, 0, HAS_LEFT_JUSTIFIED__(flags), strlen(str)) ;
count += vp_simple_str_ (str) ;
count += vp_after_number_ (min_width, count, HAS_LEFT_JUSTIFIED__ (flags)) ;
return count ;
}
#define DEFAULT_PRECISION__ 6
static int vp_double_ (const char *format, va_list *args, char type) {
double val ;
int min_width, precision ;
int count ;
enum flag_t flags ;
get_format_parts_ (format, &flags, &min_width, &precision, args) ;
min_width = (min_width < 0) ? 0 : min_width ;
precision = (precision < 0) ? DEFAULT_PRECISION__ : precision ;
val = va_arg(*args, double) ;
if (isnan(val)) {
return vp_double_nan_or_inf_ (NAN_STR__, flags, min_width) ;
}
else if (!isfinite(val)) {
return vp_double_nan_or_inf_ (INF_STR__, flags, min_width) ;
}
switch (type) {
case 'e': count = vp_double_e_ (val, flags, min_width, precision, (toupper(type) == type)) ; break ;
case 'g': count = vp_double_g_ (val, flags, min_width, precision, (toupper(type) == type)) ; break ;
case 'f':
default : count = vp_double_f_ (val, flags, min_width, precision) ;
}
return count ;
}
void vp_store_count_ (const char *format,
va_list *args,
int count) {
int *n = va_arg (*args, int*) ;
*n = count ;
}
/* Get the type and specification of the format :
type is set to a format type (d, f, x, X, etc... )
spe is set to a specification (l, h, L for ll) if one is found, otherwize it is set to '\0'
The function returns a pointer to the type character (i.e containing format type information) from the format parameter */
static const char *get_type_and_spe_ (const char *format, char *type, char *spe) {
*spe = '\0' ;
for (; *format != '\0' && !is_format_(*format); ++format) {
if (is_spe_(*format)) {
*spe = *format ;
format ++ ;
#ifdef VP_C99__
if (*spe == 'l' && *format == 'l') {
*spe = 'L' ;
format ++ ;
}
#endif
break ;
}
}
*type = *format ;
return format ;
}
static int vprintf_ (const char *format, va_list args) {
int count = 0 ;
char f, s ;
const char *tmp ;
for (; *format != '\0'; format++) {
switch (*format) {
case '%':
++ format ;
tmp = get_type_and_spe_ (format, &f, &s) ;
switch (f) {
case 'd':
case 'i': count += vp_li_ (format, &args, s) ; break ;
case 's': count += vp_str_ (format, &args, s) ; break ;
case 'c': count += vp_char_ (format, &args, s) ; break ;
case 'u': count += vp_ulli_ (format, &args, s) ; break ;
case 'x': count += vp_ullx_ (format, &args, s, 0) ; break ;
case 'X': count += vp_ullx_ (format, &args, s, 1) ; break ;
case 'p': count += vp_ptr_ (format, &args, s) ; break ;
case 'o': count += vp_ullo_ (format, &args, s) ; break ;
case 'f':
case 'e':
case 'E':
case 'g':
case 'G': count += vp_double_ (format, &args, f) ; break ;
case 'n': vp_store_count_ (format, &args, count) ; break ;
case '%':
default: putchar(*format) ; count ++ ;
}
format = (*tmp) ? tmp : format ;
break ;
default:
putchar(*format) ;
++ count ;
}
}
return count ;
}
int my_printf (const char *format, ...) {
int r ;
va_list args ;
va_start(args, format) ;
r = vprintf_ (format, args) ;
va_end(args) ;
return r ;
}
Je repasserais de toute façon beaucoup de temps sur ce code, pour le rendre plus acceuillant
J'attends tout vos commentaires
Edit : Et pour ceux qui se demanderait pourquoi je vais des trucs du genre va_arg(args,int) au lieu de va_arg(args,char), c'est tout simplement parce que mon compilateur (gcc) me dit que les char (short, wchar_t) sont passés en tant que int à la fonction lorsqu'on utilise une fonction à nombre d'arguments variables
Edit 2 : Vu que j'avais fait une énoooooorrmmmmeeee erreur, corrigé (va_list* au lieu de va_list ).
Edit 3 : Formatage des noms de fonctions :
vp_xx_ : fonction qui attend une va_list et un format (sous forme de constchar*), elle s'occupe de parser le format et d'appeler la fonction vp_format_xx_ correspondante (xx pouvant être str, char, ulli (unsigned), lli (signed), etc... Cette fonction peut également recevoir des arguments supplémentaires pour des cas particulier (vp_ullx_ par exemple)
vp_format_xx_ : fonction qui fait l'affichage. Elle attend plusieurs arguments spécifiant le format (affichage du signe ? justification à gauche ? etc... ) ainsi qu'une valeur sous forme typé (ce n'est plus une va_list !)
Edit 4 : Grosse update sur les formats (tous gérés sauf %n et les formats flottants), et modifications de la gestion des fonctions avancés (fonction globale, maccro, flags, etc... )
Edit 5 : Quelques modifs et ajouts de la gestion des formats flottants (en béta :P).
Edit : Et pour ceux qui se demanderait pourquoi je vais des trucs du genre va_arg(args,int) au lieu de va_arg(args,char), c'est tout simplement parce que mon compilateur (gcc) me dit que les char (short, wchar_t) sont passés en tant que int à la fonction lorsqu'on utilise une fonction à nombre d'arguments variables
Cette fonction serait une mini-brique du gros projet qu'est printf, elle permettrait de gérer ce genre de cas: printf("[%-5.2s]", bonjour); /* qui donne [bo...] ('.' = ' ') */
Ce n'est pas cette fonction qui parse le "%-5.2s" mais c'est elle qui affiche (qui met dans le buffer) la chaîne finale.
Ici, en l'occurence elle recevrait : flag_s(data, "bonjour", 5, 2, true);
Dans un premier temps, pour coder printf, il nous faut chacune de ces briques.
Après il faudra la fonction qui parse un "%-5.2s" ou un "%0+3.2f" etc.
Et c'est en tout dernier qu'on assemble le tout.
Donc voir un switch avec %d %s %u etc. ça fait pas super :S
Edit : Et pour ceux qui se demanderait pourquoi je vais des trucs du genre va_arg(args,int) au lieu de va_arg(args,char), c'est tout simplement parce que mon compilateur (gcc) me dit que les char (short, wchar_t) sont passés en tant que int à la fonction lorsqu'on utilise une fonction à nombre d'arguments variables
C'est surtout l'ABI qui dit ca, il me semble.
Je n'ai pas dit que c'était le compilateur qui choisissait J'ai dit que c'était lui qui me l'avait appris Merci de l'information supplémentaire en revanche
Citation : Mr21
Au sujet de printf là.
Le début que nous a informaticienZero me paraît avoir concu à l'envers (j'ai compris que c'était un truc rapide hein).
Par exemple ne serait-ce pour le flag %s nous devrions avoir une fonction qui se charge de ça.
Cette fonction serait une mini-brique du gros projet qu'est printf, elle permettrait de gérer ce genre de cas: printf("[%-5.2s]", bonjour); /* qui donne [bo...] ('.' = ' ') */
Ce n'est pas cette fonction qui parse le "%-5.2s" mais c'est elle qui affiche (qui met dans le buffer) la chaîne finale.
Ici, en l'occurence elle recevrait : flag_s(data, "bonjour", 5, 2, true);
Dans un premier temps, pour coder printf, il nous faut chacune de ces briques.
Après il faudra la fonction qui parse un "%-5.2s" ou un "%0+3.2f" etc.
Et c'est en tout dernier qu'on assemble le tout.
Donc voir un switch avec %d %s %u etc. ça fait pas super :S
C'est comme cela que je suis parti aussi, à partir du moment où la boucle principale ne gère pas 90% du formattage, elle reste assez fluide (que ce soit avec des case ou des pointeurs de fonctions ).
Non sérieusement, ça me parait correct, j'ai regardé chez moi (MinGW) et j'ai un limits.h avec des valeurs en bruts et un autres qui utilise d'autre define du style :
Intéressant ton code, je ne connaissais pas la macro __func__. Sinon pour participer aussi, voici une partie de string :
Halte-là !
S'il s'agit de refaire la libC, et pas juste un exercice de tutoriel, alors il faut commencer par lire la documentation officielle avant de coder.
Parce qu'à ce que je vois, my_strncpy, my_strchr, my_strncat, my_strcmp, my_strncmp et my_strstr n'implémentent pas ce qui est demandé par la norme.
S'il s'agit de refaire la libC, et pas juste un exercice de tutoriel, alors il faut commencer par lire la documentation officielle avant de coder.
Parce qu'à ce que je vois, my_strncpy, my_strchr, my_strncat, my_strcmp, my_strncmp et my_strstr n'implémentent pas ce qui est demandé par la norme.
Comme il s'agit d'un projet qui finira au fond de mon disque dur et que j'ouvrirais plus jamais de ma vie, je me suis pas posé la question, et effectivement j'ai triché par rapport au standard.
@fscorpio : Uhm, mais on peut pas l'implémenter <limits.h>. Quelles certitudes as-tu sur leur valeur ? Par exemple, pourquoi CHAR_BIT aurait pour valeur 8, sans savoir quelle architecture tu vas viser ?
@Holt : Je n'ai pas trop regardé, je le ferai peut-être dans la journée.
Allez, <stdbool.h> pour la forme.
/* The header `<stdbool.h>' defines four macros. */
#ifndef H_LP_STDBOOL_20120802105923
# define H_LP_STDBOOL_20120802105923
/* The macro `bool' expands to `_Bool'. */
# define bool _Bool
/*
* The remaining three macros are suitable for use in `#if'
* preprocessing directives. They are :
* - `true', which expands to the integer constant 1 ;
* - `false', which expands to the integer constant 0 ;
* - `__bool_true_false_are_defined', which expands to the integer
* constant 1.
*/
# define true 1
# define false 0
# define __bool_true_false_are_defined 1
#endif /* H_LP_STDBOOL_20120802105923 */
@fscorpio : Uhm, mais on peut pas l'implémenter <limits.h>. Quelles certitudes as-tu sur leur valeur ? Par exemple, pourquoi CHAR_BIT aurait pour valeur 8, sans savoir quelle architecture tu vas viser ?
Histoire de pas vous prendre la tête avec toutes ces histoires de portabilité, pourquoi ne pas choisir une architecture et une plate-forme cibles une bonne fois pour toutes ? Au hasard, du x86 (c'est probablement l'archi la plus courante sur les machines des zéros), donc 8 bits/octets, short = 2 octets, int = 4 octets, et ainsi de suite. Au moins, on est fixés et on peut avancer.
J'ai déménagé sur Zeste de savoir — Ex-manager des modérateurs.
Tout à fait d'accord avec GuilOooo, le mieux serait de se baser sur une architecture fixe.
J'ai édité mon post pour ajouter les formats de printf manquants, normalement ils sont tous présents maintenant (sauf le %n) ! (pfiou )
Les formats entiers sont normalement OK (by my test, mais à confirmer, j'attends toutes remarques / non confirmités, etc... ).
En ce qui concerne les formats flottants, ils sont assez proche de la représentation du printf standard chez moi, à 1-3 digits près (je me pencherais là dessus, je pense que l'implémentation de mingw fait une approximation avant de rajouter des 0, ce que je ne fais pas... ). Le format %g n'est pas "correct", il va simplement choisir entre e et f en fonction de la valeur... Je n'ai pas eu le temps de regarder sur internet les spécifications précises (s'il y en a) de ce format.
@Holt : J'aime bien le code dans son ensemble. Du point de vue de la conception, il y a AMHA quelques répétitions (et des branchements conditionnels identiques qui sont testés plusieurs fois). À vue d'œil, tu utilises le C99 donc tu pourrais également utiliser intmax_t et uintmax_t pour stocker des nombres de n'importe quelle taille. Enfin (avis personnel), il serait intéressant d'utiliser des émulations de boucle parfois. Par exemple, si je prends la récupération des flags, ta version :
enum flag_t {
FLAG_LJUST = 0x01,
FLAG_PSIGN = 0x02,
FLAG_SPACE = 0x04,
FLAG_AFORM = 0x08,
FLAG_ZEROS = 0x16
};
int is_flag_ (char c) {
return c == '-'
|| c == '0'
|| c == '+'
|| c == ' '
|| c == '#' ;
}
static enum flag_t get_flags(const char *s)
{
enum flag_t flag = 0;
for (; is_flag(*s); ++s) {
switch (*s) {
case '-': flag |= FLAG_LJUST; break;
case '+': flag |= FLAG_PSIGN; break;
case ' ': flag |= FLAG_SPACE; break;
case '+': flag |= FLAG_AFORM; break;
case '0': flag |= FLAG_ZEROS; break;
}
}
return flag;
}
Personnellement, j'aurais utilisé goto (haha, vous avez peur, hein ? ) pour éviter les répétitions.
enum flag_t {
FLAG_LJUST = 0x01,
FLAG_PSIGN = 0x02,
FLAG_SPACE = 0x04,
FLAG_AFORM = 0x08,
FLAG_ZEROS = 0x16
};
static enum flag_t get_flags(const char *s)
{
enum flag_t flag = 0;
start:
switch (*s++) {
case '-': flag |= FLAG_LJUST; goto start;
case '+': flag |= FLAG_PSIGN; goto start;
case ' ': flag |= FLAG_SPACE; goto start;
case '#': flag |= FLAG_AFORM; goto start;
case '0': flag |= FLAG_ZEROS; goto start;
}
return flag;
}
@Holt : J'aime bien le code dans son ensemble. Du point de vue de la conception, il y a AMHA quelques répétitions (et des branchements conditionnels identiques qui sont testés plusieurs fois). À vue d'œil, tu utilises le C99 donc tu pourrais également utiliser intmax_t et uintmax_t pour stocker des nombres de n'importe quelle taille. Enfin (avis personnel), il serait intéressant d'utiliser des émulations de boucle parfois. Par exemple, si je prends la récupération des flags, ta version :
enum flag_t {
FLAG_LJUST = 0x01,
FLAG_PSIGN = 0x02,
FLAG_SPACE = 0x04,
FLAG_AFORM = 0x08,
FLAG_ZEROS = 0x16
};
int is_flag_ (char c) {
return c == '-'
|| c == '0'
|| c == '+'
|| c == ' '
|| c == '#' ;
}
static enum flag_t get_flags(const char *s)
{
enum flag_t flag = 0;
for (; is_flag(*s); ++s) {
switch (*s) {
case '-': flag |= FLAG_LJUST; break;
case '+': flag |= FLAG_PSIGN; break;
case ' ': flag |= FLAG_SPACE; break;
case '+': flag |= FLAG_AFORM; break;
case '0': flag |= FLAG_ZEROS; break;
}
}
return flag;
}
Personnellement, j'aurais utilisé goto (haha, vous avez peur, hein ? ) pour éviter les répétitions.
enum flag_t {
FLAG_LJUST = 0x01,
FLAG_PSIGN = 0x02,
FLAG_SPACE = 0x04,
FLAG_AFORM = 0x08,
FLAG_ZEROS = 0x16
};
static enum flag_t get_flags(const char *s)
{
enum flag_t flag = 0;
start:
switch (*s++) {
case '-': flag |= FLAG_LJUST; goto start;
case '+': flag |= FLAG_PSIGN; goto start;
case ' ': flag |= FLAG_SPACE; goto start;
case '+': flag |= FLAG_AFORM; goto start;
case '0': flag |= FLAG_ZEROS; goto start;
}
return flag;
}
C'est vrai que j'ai pas pensé au enum, j'ai plus l'habitude de les utiliser en C++ et d'utiliser les defines en C, je vais rester comme ça pour le moment
Au niveau du goto, ton code ne me dérange pas, je suppose qu'il doit être plus rapide à l'exécution, je testerais
En fait j'ai testé la compilation c90, c99 et c11, il se trouve qu'effectivemment en c90 il n'aime pas trop les longlong, je ferais peut être un p'tit coup de compil conditionnel si l'envie me prend Je ne connaissais pas intmax_t et uintmax_t, je vais regarder
J'ai peut être fait des modifications depuis que tu as regardé d'ailleurs, je viens de me rendre compte que printf n'a pas le même fonctionnement si l'ont spécifie l'option -ansi ou non au compilateur, c'est embetant pour moi
Apparemment la version non ANSI (ainsi que la mienne), bourre de '0' une fois que la représentation machine n'existe plus, alors que la version ANSI bourre avec un peu n'importe quoi (j'avais le même résultat quand je n'avais pas de condition et quand j'utilisais fmod en boucle... C'est très aléatoire, en gros fmod(654274561789716869789189718717897100000000000,10) peut prendre à peu près toutes les valeurs entre 0 et 10 :P)
C'est vrai que j'ai pas pensé au enum, j'ai plus l'habitude de les utiliser en C++ et d'utiliser les defines en C, je vais rester comme ça pour le moment
D'ailleurs, pour être un peu plus esthétique, tu pourrais utiliser directement les décalages de bits (puisqu'au final c'est ce qu'on cherche à obtenir, un booléen pour chaque bit), qui seront de toute manière directement remplacés par le compilateur.
Parce que devrais savoir, à force, que j'aime bien les goto. Le problème avec ton code c'est qu'il s'arrête au '\0', or, il faut continuer tant qu'il y a encore des flags. Tu noteras que je n'ai obligé personne, j'ai simplement proposé une solution qui peut être potentiellement préférable. Pour les deux '+', c'est une faute de frappe.
Parce que devrais savoir, à force, que j'aime bien les goto. Tu noteras que je n'ai obligé personne, j'ai simplement proposé une solution qui peut être potentiellement préférable. Pour les deux '+', c'est une faute de frappe.
Ah mais j'ai jamais critiqué ton choix hein Moi aussi j'aime bien les goto, mais la le default me parait convenir parfaitement.
Citation : Lucas-84
Le problème avec ton code c'est qu'il s'arrête au '\0', or, il faut continuer tant qu'il y a encore des flags.
alors là je comprend pas ce que tu veux dire. Si il y a un '\0' et que tu continue à lire tu segfault.
D'ailleurs ton code s'arretera aussi au '\0' ...
Si debugger, c’est supprimer des bugs, alors programmer ne peut être que les ajouter - Edsger Dijkstra
Vos 2 codes sont corrects il me semble, en revanche il faudrait passer un constchar** pour qu'au retour dans la fonction appelante, on puisse lire la suite (sinon ça oblige à relire tous les flags dans la fonction appelante ><).
J'ai modifié le code en utilisant les enum, sans goto (désolé Lucas ). Je le posterais un peu après, je planche sur l'amélioration de la représentation flottante
Non, il s'arrêtera quand le caractère courant n'est ni un '+', ni un '-', ni un '0', ni un ' ', ni un '#'.
Bah comme le mien
Certes (je l'avais parcouru un peu rapidement en fait), mais le tien contient plusieurs points de sortie, dont des répétitions de code retour. Or, généralement on préfère faire avec du <acronym title="Simple entry, simple exit">SESE</acronym>. J'ai personnellement horreur des return en plein milieu du code de la fonction, et je ne suis pas le seul à avoir cet avis (même si il y en a aussi d'autres qui s'en fichent).
Certes (je l'avais parcouru un peu rapidement en fait), mais le tien contient plusieurs points de sortie, dont des répétitions de code retour. Or, généralement on préfère faire avec du <acronym title="Simple entry, simple exit">SESE</acronym>. J'ai personnellement horreur des return en plein milieu du code de la fonction, et je ne suis pas le seul à avoir cet avis (même si il y en a aussi d'autres qui s'en fichent).
Je ne voudrais pas te laisser mal à l'aise, donc je te propose la même fonction avec un seul point de sortie
enum flag_t {
FLAG_LJUST = 0x01,
FLAG_PSIGN = 0x02,
FLAG_SPACE = 0x04,
FLAG_AFORM = 0x08,
FLAG_ZEROS = 0x16
};
static enum flag_t get_flags(const char *s)
{
enum flag_t flag = 0;
for (; ; ++s) {
switch (*s) {
case '-': flag |= FLAG_LJUST; break;
case '+': flag |= FLAG_PSIGN; break;
case ' ': flag |= FLAG_SPACE; break;
case '+': flag |= FLAG_AFORM; break;
case '0': flag |= FLAG_ZEROS; break;
case '#' : break;
default : return (flag);
}
}
}
En faite le second return était issue de ton code (moi je les écrit avec des () ), et n'était de toute façon jamais atteint.
Si debugger, c’est supprimer des bugs, alors programmer ne peut être que les ajouter - Edsger Dijkstra
Donc là on a quelqu'un qui râle pour deux return avec 441 goto dans ses codes?
tellement de compétition ici
Bah écoute, le jour où ta fonction fera un traitement supplémentaire, tu seras bien content d'avoir un flot simple à suivre.
@damjuve : Bon, c'est déjà mieux (je n'ai pas dit que l'utilisation des goto était la seule solution, hein). Mais maintenant, si je veux faire un traitement après la boucle, je mets tout le code dans le default ? C'est pas très esthétique.
Je suis pas contre les goto, mais si tu utilises un constchar**, il faut faire un double dé-référencement et une post incrémentation sur la valeur simplement dé-référencé (oula ma phrase veut dire quelque chose ? ) dans le switch...