C语言可变参数,函数指针及回调函数
保命声明:笔者代码能力有限,若行文中有错漏之处欢迎大家指出。
C语言可变参数
[https://mp.weixin.qq.com/s/ydhK8HYuRD0lZazPsPxsvg]
c/c++语言具备一个不同于其他编程语言的的特性,即支持可变参数。
例如C库中的printf,scanf等函数,都支持输入数量不定的参数。printf函数原型为 int printf(const char *format, …);
printf("hello world"); ////< 1个参数
printf("%d", a); ////< 2个参数
printf("%d, %d", a, b); ////< 3个参数
测试
main.c
#include <stdio.h>
#include <stdarg.h>
#define uint8_t unsigned char
#define uint16_t unsigned short
#define uint32_t unsigned int
uint8_t add(uint8_t arg_num, ... ){
uint8_t _result=0;
uint32_t m= -0x7FFFFFFF;//32位系统最小整数
uint16_t _m=-0x7FFF;//16位系统最小整数
uint8_t __m=-0x7F;//8位系统最小整数
va_list ap;//可变参数表指针
va_start (ap , arg_num);//取得可变参数表首地址
//遍历可变参数表
for( uint8_t i=0;i<arg_num;i++){
uint8_t t=va_arg(ap,uint8_t);//t依次对应参数
_result += t;
}
va_end(ap);
/*
va_end ( list )实际上被定义为空,没有任何真实对应的代码,用于代码对称,与 va_start对应
*/
return _result;
}
int main(void){
printf("Add_result:%d\n",add(3,1,3,5));
return 0;
}
结果:
C语言使用可变参数列表实现printf(my_printf)
[https://blog.51cto.com/shaungqiran/1681698]
//使用可变参数列表实现print("s\t c\n","bit-tech",'w');
#include<stdio.h>
#include<stdarg.h>
void int_to_char(int num)
{
if ((num / 10) > 0)
int_to_char(num / 10);
putchar(num %10+ 48);
}
void my_print(char p[],...)
{
char *str1 = p;
int num = 0;
char *pVal;
va_list str;
va_start(str,p);
while (*str1)
{
if (*str1 == '%'&&*(str1 + 1) != '\0')
{
str1++;
if (*str1 == 'd')
{
num = va_arg(str, int);
int_to_char(num);
}
if (*str1 == 's')
{
pVal= (char *)va_arg(str, char *);
while (*pVal)
{
putchar(*pVal);
pVal++;
}
}
if (*str1 == 'c')
{
putchar(va_arg(str, char));
}
}
else if(*str1 == '\n')
{
putchar('\n');
}
else if (*str1 == '\t')
{
putchar('\t');
}
else
putchar(*str1);
str1++;
}
va_end(str);
}
int main()
{
int a=1010;
char b[20]="我是帅哥!";
char c = '#';
my_print("你\n%c是猪\n%d\n哈哈\n%s\n\t<-tab\n",c ,a,b);
return 0;
}
结果:
printf函数原型
[https://www.cnblogs.com/LuanShiLiuNian/articles/15986712.html]
[https://gcc.gnu.org]
[https://github.com/embeddedartistry/libc]
[https://github.com/embeddedartistry/printf/tree/3014f5fc58bf2f1f4f4aa09300e6cbf9e7a76a1d]
来源:stdio.h
//macOS,XCode
int printf(const char * __restrict, ...) __printflike(1, 2);
//Windows,Visual Studio
_Check_return_opt_
_CRT_STDIO_INLINE int __CRTDECL printf(
_In_z_ _Printf_format_string_ char const* const _Format,
...)
int printf( const char* format , [argument] ... );
C语言函数指针
[https://mp.weixin.qq.com/s/B1-owxujY-F3X3BrYyd-3A]
函数指针是指向函数的指针变量。
通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。
函数指针可以像一般函数一样,用于调用函数、传递参数。
函数指针的定义方式为:
函数返回值类型 (* 指针变量名) (函数参数列表);
注意:指向函数的指针变量没有++和--运算
测试
/*
函数指针
*/
#include <stdio.h>
#include <stdarg.h>
#define uint8_t unsigned char
#define uint16_t unsigned short
#define uint32_t unsigned int
int Max(int, int); //函数声明
int main(void)
{
int(*p_Max)(int, int); //定义一个函数指针
int a, b, c;
p_Max = &Max; //把函数Max赋给指针变量p, 使p指向Max函数
printf("please enter a and b:");
scanf("%d%d", &a, &b);
c = p_Max(a, b); //通过函数指针调用Max函数
printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
return 0;
}
int Max(int x, int y) //定义Max函数
{
int z=-0x7FFFFFFF;//32位最小整数
if (x > y)
{
z = x;
}
else
{
z = y;
}
return z;
}
计算机最大整数
原码、反码和补码在表示正整数时相同(最高位为0)
32位:
16位:
8位:
C语言回调函数
[https://mp.weixin.qq.com/s/B1-owxujY-F3X3BrYyd-3A]
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
测试
#include <stdio.h>
#include <stdlib.h>
typedef void(*FunType)(int);
//前加一个typedef关键字,这样就定义一个名为FunType函数指针类型,而不是一个FunType变量。
//形式同 typedef int* PINT;
void myFun(int x);
void hisFun(int x);
void herFun(int x);
void callFun(FunType fp,int x);
int main()
{
callFun(myFun,100);//传入函数指针常量,作为回调函数
callFun(hisFun,200);
callFun(herFun,300);
return 0;
}
void callFun(FunType fp,int x)
{
fp(x);//通过fp的指针执行传递进来的函数,注意fp所指的函数有一个参数
}
void myFun(int x)
{
printf("myFun: %d\n",x);
}
void hisFun(int x)
{
printf("hisFun: %d\n",x);
}
void herFun(int x)
{
printf("herFun: %d\n",x);
}
结果:
附录
stdio.h文件
/*
* Copyright (c) 2000, 2005, 2007, 2009, 2010 Apple Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
/*-
* Copyright (c) 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Chris Torek.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)stdio.h 8.5 (Berkeley) 4/29/95
*/
#ifndef _STDIO_H_
#define _STDIO_H_
#include <_stdio.h>
__BEGIN_DECLS
extern FILE *__stdinp;
extern FILE *__stdoutp;
extern FILE *__stderrp;
__END_DECLS
#define __SLBF 0x0001 /* line buffered */
#define __SNBF 0x0002 /* unbuffered */
#define __SRD 0x0004 /* OK to read */
#define __SWR 0x0008 /* OK to write */
/* RD and WR are never simultaneously asserted */
#define __SRW 0x0010 /* open for reading & writing */
#define __SEOF 0x0020 /* found EOF */
#define __SERR 0x0040 /* found error */
#define __SMBF 0x0080 /* _buf is from malloc */
#define __SAPP 0x0100 /* fdopen()ed in append mode */
#define __SSTR 0x0200 /* this is an sprintf/snprintf string */
#define __SOPT 0x0400 /* do fseek() optimisation */
#define __SNPT 0x0800 /* do not do fseek() optimisation */
#define __SOFF 0x1000 /* set iff _offset is in fact correct */
#define __SMOD 0x2000 /* true => fgetln modified _p text */
#define __SALC 0x4000 /* allocate string space dynamically */
#define __SIGN 0x8000 /* ignore this file in _fwalk */
/*
* The following three definitions are for ANSI C, which took them
* from System V, which brilliantly took internal interface macros and
* made them official arguments to setvbuf(), without renaming them.
* Hence, these ugly _IOxxx names are *supposed* to appear in user code.
*
* Although numbered as their counterparts above, the implementation
* does not rely on this.
*/
#define _IOFBF 0 /* setvbuf should set fully buffered */
#define _IOLBF 1 /* setvbuf should set line buffered */
#define _IONBF 2 /* setvbuf should set unbuffered */
#define BUFSIZ 1024 /* size of buffer used by setbuf */
#define EOF (-1)
/* must be == _POSIX_STREAM_MAX <limits.h> */
#define FOPEN_MAX 20 /* must be <= OPEN_MAX <sys/syslimits.h> */
#define FILENAME_MAX 1024 /* must be <= PATH_MAX <sys/syslimits.h> */
/* System V/ANSI C; this is the wrong way to do this, do *not* use these. */
#ifndef _ANSI_SOURCE
#define P_tmpdir "/var/tmp/"
#endif
#define L_tmpnam 1024 /* XXX must be == PATH_MAX */
#define TMP_MAX 308915776
#ifndef SEEK_SET
#define SEEK_SET 0 /* set file offset to offset */
#endif
#ifndef SEEK_CUR
#define SEEK_CUR 1 /* set file offset to current plus offset */
#endif
#ifndef SEEK_END
#define SEEK_END 2 /* set file offset to EOF plus offset */
#endif
#define stdin __stdinp
#define stdout __stdoutp
#define stderr __stderrp
#ifdef _DARWIN_UNLIMITED_STREAMS
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_3_2
#error "_DARWIN_UNLIMITED_STREAMS specified, but -miphoneos-version-min version does not support it."
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_6
#error "_DARWIN_UNLIMITED_STREAMS specified, but -mmacosx-version-min version does not support it."
#endif
#endif
/* ANSI-C */
__BEGIN_DECLS
void clearerr(FILE *);
int fclose(FILE *);
int feof(FILE *);
int ferror(FILE *);
int fflush(FILE *);
int fgetc(FILE *);
int fgetpos(FILE * __restrict, fpos_t *);
char *fgets(char * __restrict, int, FILE *);
#if defined(_DARWIN_UNLIMITED_STREAMS) || defined(_DARWIN_C_SOURCE)
FILE *fopen(const char * __restrict __filename, const char * __restrict __mode) __DARWIN_ALIAS_STARTING(__MAC_10_6, __IPHONE_3_2, __DARWIN_EXTSN(fopen));
#else /* !_DARWIN_UNLIMITED_STREAMS && !_DARWIN_C_SOURCE */
FILE *fopen(const char * __restrict __filename, const char * __restrict __mode) __DARWIN_ALIAS_STARTING(__MAC_10_6, __IPHONE_2_0, __DARWIN_ALIAS(fopen));
#endif /* (DARWIN_UNLIMITED_STREAMS || _DARWIN_C_SOURCE) */
int fprintf(FILE * __restrict, const char * __restrict, ...) __printflike(2, 3);
int fputc(int, FILE *);
int fputs(const char * __restrict, FILE * __restrict) __DARWIN_ALIAS(fputs);
size_t fread(void * __restrict __ptr, size_t __size, size_t __nitems, FILE * __restrict __stream);
FILE *freopen(const char * __restrict, const char * __restrict,
FILE * __restrict) __DARWIN_ALIAS(freopen);
int fscanf(FILE * __restrict, const char * __restrict, ...) __scanflike(2, 3);
int fseek(FILE *, long, int);
int fsetpos(FILE *, const fpos_t *);
long ftell(FILE *);
size_t fwrite(const void * __restrict __ptr, size_t __size, size_t __nitems, FILE * __restrict __stream) __DARWIN_ALIAS(fwrite);
int getc(FILE *);
int getchar(void);
char *gets(char *);
void perror(const char *) __cold;
int printf(const char * __restrict, ...) __printflike(1, 2);
int putc(int, FILE *);
int putchar(int);
int puts(const char *);
int remove(const char *);
int rename (const char *__old, const char *__new);
void rewind(FILE *);
int scanf(const char * __restrict, ...) __scanflike(1, 2);
void setbuf(FILE * __restrict, char * __restrict);
int setvbuf(FILE * __restrict, char * __restrict, int, size_t);
int sprintf(char * __restrict, const char * __restrict, ...) __printflike(2, 3) __swift_unavailable("Use snprintf instead.");
int sscanf(const char * __restrict, const char * __restrict, ...) __scanflike(2, 3);
FILE *tmpfile(void);
__swift_unavailable("Use mkstemp(3) instead.")
#if !defined(_POSIX_C_SOURCE)
__deprecated_msg("This function is provided for compatibility reasons only. Due to security concerns inherent in the design of tmpnam(3), it is highly recommended that you use mkstemp(3) instead.")
#endif
char *tmpnam(char *);
int ungetc(int, FILE *);
int vfprintf(FILE * __restrict, const char * __restrict, va_list) __printflike(2, 0);
int vprintf(const char * __restrict, va_list) __printflike(1, 0);
int vsprintf(char * __restrict, const char * __restrict, va_list) __printflike(2, 0) __swift_unavailable("Use vsnprintf instead.");
__END_DECLS
/* Additional functionality provided by:
* POSIX.1-1988
*/
#if __DARWIN_C_LEVEL >= 198808L
#define L_ctermid 1024 /* size for ctermid(); PATH_MAX */
__BEGIN_DECLS
#include <_ctermid.h>
#if defined(_DARWIN_UNLIMITED_STREAMS) || defined(_DARWIN_C_SOURCE)
FILE *fdopen(int, const char *) __DARWIN_ALIAS_STARTING(__MAC_10_6, __IPHONE_3_2, __DARWIN_EXTSN(fdopen));
#else /* !_DARWIN_UNLIMITED_STREAMS && !_DARWIN_C_SOURCE */
FILE *fdopen(int, const char *) __DARWIN_ALIAS_STARTING(__MAC_10_6, __IPHONE_2_0, __DARWIN_ALIAS(fdopen));
#endif /* (DARWIN_UNLIMITED_STREAMS || _DARWIN_C_SOURCE) */
int fileno(FILE *);
__END_DECLS
#endif /* __DARWIN_C_LEVEL >= 198808L */
/* Additional functionality provided by:
* POSIX.2-1992 C Language Binding Option
*/
#if TARGET_OS_IPHONE
#define __swift_unavailable_on(osx_msg, ios_msg) __swift_unavailable(ios_msg)
#else
#define __swift_unavailable_on(osx_msg, ios_msg) __swift_unavailable(osx_msg)
#endif
#if __DARWIN_C_LEVEL >= 199209L
__BEGIN_DECLS
int pclose(FILE *) __swift_unavailable_on("Use posix_spawn APIs or NSTask instead.", "Process spawning is unavailable.");
#if defined(_DARWIN_UNLIMITED_STREAMS) || defined(_DARWIN_C_SOURCE)
FILE *popen(const char *, const char *) __DARWIN_ALIAS_STARTING(__MAC_10_6, __IPHONE_3_2, __DARWIN_EXTSN(popen)) __swift_unavailable_on("Use posix_spawn APIs or NSTask instead.", "Process spawning is unavailable.");
#else /* !_DARWIN_UNLIMITED_STREAMS && !_DARWIN_C_SOURCE */
FILE *popen(const char *, const char *) __DARWIN_ALIAS_STARTING(__MAC_10_6, __IPHONE_2_0, __DARWIN_ALIAS(popen)) __swift_unavailable_on("Use posix_spawn APIs or NSTask instead.", "Process spawning is unavailable.");
#endif /* (DARWIN_UNLIMITED_STREAMS || _DARWIN_C_SOURCE) */
__END_DECLS
#endif /* __DARWIN_C_LEVEL >= 199209L */
#undef __swift_unavailable_on
/* Additional functionality provided by:
* POSIX.1c-1995,
* POSIX.1i-1995,
* and the omnibus ISO/IEC 9945-1: 1996
*/
#if __DARWIN_C_LEVEL >= 199506L
/* Functions internal to the implementation. */
__BEGIN_DECLS
int __srget(FILE *);
int __svfscanf(FILE *, const char *, va_list) __scanflike(2, 0);
int __swbuf(int, FILE *);
__END_DECLS
/*
* The __sfoo macros are here so that we can
* define function versions in the C library.
*/
#define __sgetc(p) (--(p)->_r < 0 ? __srget(p) : (int)(*(p)->_p++))
#if defined(__GNUC__) && defined(__STDC__)
__header_always_inline int __sputc(int _c, FILE *_p) {
if (--_p->_w >= 0 || (_p->_w >= _p->_lbfsize && (char)_c != '\n'))
return (*_p->_p++ = _c);
else
return (__swbuf(_c, _p));
}
#else
/*
* This has been tuned to generate reasonable code on the vax using pcc.
*/
#define __sputc(c, p) \
(--(p)->_w < 0 ? \
(p)->_w >= (p)->_lbfsize ? \
(*(p)->_p = (c)), *(p)->_p != '\n' ? \
(int)*(p)->_p++ : \
__swbuf('\n', p) : \
__swbuf((int)(c), p) : \
(*(p)->_p = (c), (int)*(p)->_p++))
#endif
#define __sfeof(p) (((p)->_flags & __SEOF) != 0)
#define __sferror(p) (((p)->_flags & __SERR) != 0)
#define __sclearerr(p) ((void)((p)->_flags &= ~(__SERR|__SEOF)))
#define __sfileno(p) ((p)->_file)
__BEGIN_DECLS
void flockfile(FILE *);
int ftrylockfile(FILE *);
void funlockfile(FILE *);
int getc_unlocked(FILE *);
int getchar_unlocked(void);
int putc_unlocked(int, FILE *);
int putchar_unlocked(int);
/* Removed in Issue 6 */
#if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 200112L
int getw(FILE *);
int putw(int, FILE *);
#endif
__swift_unavailable("Use mkstemp(3) instead.")
#if !defined(_POSIX_C_SOURCE)
__deprecated_msg("This function is provided for compatibility reasons only. Due to security concerns inherent in the design of tempnam(3), it is highly recommended that you use mkstemp(3) instead.")
#endif
char *tempnam(const char *__dir, const char *__prefix) __DARWIN_ALIAS(tempnam);
__END_DECLS
#ifndef lint
#define getc_unlocked(fp) __sgetc(fp)
#define putc_unlocked(x, fp) __sputc(x, fp)
#endif /* lint */
#define getchar_unlocked() getc_unlocked(stdin)
#define putchar_unlocked(x) putc_unlocked(x, stdout)
#endif /* __DARWIN_C_LEVEL >= 199506L */
/* Additional functionality provided by:
* POSIX.1-2001
* ISO C99
*/
#if __DARWIN_C_LEVEL >= 200112L
#include <sys/_types/_off_t.h>
__BEGIN_DECLS
int fseeko(FILE * __stream, off_t __offset, int __whence);
off_t ftello(FILE * __stream);
__END_DECLS
#endif /* __DARWIN_C_LEVEL >= 200112L */
#if __DARWIN_C_LEVEL >= 200112L || defined(_C99_SOURCE) || defined(__cplusplus)
__BEGIN_DECLS
int snprintf(char * __restrict __str, size_t __size, const char * __restrict __format, ...) __printflike(3, 4);
int vfscanf(FILE * __restrict __stream, const char * __restrict __format, va_list) __scanflike(2, 0);
int vscanf(const char * __restrict __format, va_list) __scanflike(1, 0);
int vsnprintf(char * __restrict __str, size_t __size, const char * __restrict __format, va_list) __printflike(3, 0);
int vsscanf(const char * __restrict __str, const char * __restrict __format, va_list) __scanflike(2, 0);
__END_DECLS
#endif /* __DARWIN_C_LEVEL >= 200112L || defined(_C99_SOURCE) || defined(__cplusplus) */
/* Additional functionality provided by:
* POSIX.1-2008
*/
#if __DARWIN_C_LEVEL >= 200809L
#include <sys/_types/_ssize_t.h>
__BEGIN_DECLS
int dprintf(int, const char * __restrict, ...) __printflike(2, 3) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3);
int vdprintf(int, const char * __restrict, va_list) __printflike(2, 0) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3);
ssize_t getdelim(char ** __restrict __linep, size_t * __restrict __linecapp, int __delimiter, FILE * __restrict __stream) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3);
ssize_t getline(char ** __restrict __linep, size_t * __restrict __linecapp, FILE * __restrict __stream) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3);
FILE *fmemopen(void * __restrict __buf, size_t __size, const char * __restrict __mode) __API_AVAILABLE(macos(10.13), ios(11.0), tvos(11.0), watchos(4.0));
FILE *open_memstream(char **__bufp, size_t *__sizep) __API_AVAILABLE(macos(10.13), ios(11.0), tvos(11.0), watchos(4.0));
__END_DECLS
#endif /* __DARWIN_C_LEVEL >= 200809L */
/* Darwin extensions */
#if __DARWIN_C_LEVEL >= __DARWIN_C_FULL
__BEGIN_DECLS
extern __const int sys_nerr; /* perror(3) external variables */
extern __const char *__const sys_errlist[];
int asprintf(char ** __restrict, const char * __restrict, ...) __printflike(2, 3);
char *ctermid_r(char *);
char *fgetln(FILE *, size_t *);
__const char *fmtcheck(const char *, const char *);
int fpurge(FILE *);
void setbuffer(FILE *, char *, int);
int setlinebuf(FILE *);
int vasprintf(char ** __restrict, const char * __restrict, va_list) __printflike(2, 0);
FILE *zopen(const char *, const char *, int);
/*
* Stdio function-access interface.
*/
FILE *funopen(const void *,
int (* _Nullable)(void *, char *, int),
int (* _Nullable)(void *, const char *, int),
fpos_t (* _Nullable)(void *, fpos_t, int),
int (* _Nullable)(void *));
__END_DECLS
#define fropen(cookie, fn) funopen(cookie, fn, 0, 0, 0)
#define fwopen(cookie, fn) funopen(cookie, 0, fn, 0, 0)
#define feof_unlocked(p) __sfeof(p)
#define ferror_unlocked(p) __sferror(p)
#define clearerr_unlocked(p) __sclearerr(p)
#define fileno_unlocked(p) __sfileno(p)
#endif /* __DARWIN_C_LEVEL >= __DARWIN_C_FULL */
#ifdef _USE_EXTENDED_LOCALES_
#include <xlocale/_stdio.h>
#endif /* _USE_EXTENDED_LOCALES_ */
#if defined (__GNUC__) && _FORTIFY_SOURCE > 0 && !defined (__cplusplus)
/* Security checking functions. */
#include <secure/_stdio.h>
#endif
#endif /* _STDIO_H_ */
C语言printf子模块
[https://github.com/embeddedartistry/printf/tree/3014f5fc58bf2f1f4f4aa09300e6cbf9e7a76a1d]
[https://github.com/mpaland/printf]
printf.h
/**
* @author (c) Eyal Rozenberg <eyalroz1@gmx.com>
* 2021-2022, Haifa, Palestine/Israel
* @author (c) Marco Paland (info@paland.com)
* 2014-2019, PALANDesign Hannover, Germany
*
* @note Others have made smaller contributions to this file: see the
* contributors page at https://github.com/eyalroz/printf/graphs/contributors
* or ask one of the authors.
*
* @brief Small stand-alone implementation of the printf family of functions
* (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems with
* a very limited resources.
*
* @note the implementations are thread-safe; re-entrant; use no functions from
* the standard library; and do not dynamically allocate any memory.
*
* @license The MIT License (MIT)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef PRINTF_H_
#define PRINTF_H_
#ifdef __cplusplus
# include <cstdarg>
# include <cstddef>
extern "C" {
#else
# include <stdarg.h>
# include <stddef.h>
#endif
#ifdef __GNUC__
# define ATTR_PRINTF(one_based_format_index, first_arg) \
__attribute__((format(__printf__, (one_based_format_index), (first_arg))))
# define ATTR_VPRINTF(one_based_format_index) ATTR_PRINTF((one_based_format_index), 0)
#else
# define ATTR_PRINTF((one_based_format_index), (first_arg))
# define ATTR_VPRINTF(one_based_format_index)
#endif
#ifndef PRINTF_ALIAS_STANDARD_FUNCTION_NAMES
#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 0
#endif
#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES
# define printf_ printf
# define sprintf_ sprintf
# define vsprintf_ vsprintf
# define snprintf_ snprintf
# define vsnprintf_ vsnprintf
# define vprintf_ vprintf
#endif
// If you want to include this implementation file directly rather than
// link against, this will let you control the functions' visibility,
// e.g. make them static so as not to clash with other objects also
// using them.
#ifndef PRINTF_VISIBILITY
#define PRINTF_VISIBILITY
#endif
/**
* Prints/send a single character to some opaque output entity
*
* @note This function is not implemented by the library, only declared; you must provide an
* implementation if you wish to use the @ref printf / @ref vprintf function (and possibly
* for linking against the library, if your toolchain does not support discarding unused functions)
*
* @note The output could be as simple as a wrapper for the `write()` system call on a Unix-like
* system, or even libc's @ref putchar , for replicating actual functionality of libc's @ref printf
* function; but on an embedded system it may involve interaction with a special output device,
* like a UART, etc.
*
* @note in libc's @ref putchar, the parameter type is an int; this was intended to support the
* representation of either a proper character or EOF in a variable - but this is really not
* meaningful to pass into @ref putchar and is discouraged today. See further discussion in:
* @link https://stackoverflow.com/q/17452847/1593077
*
* @param c the single character to print
*/
PRINTF_VISIBILITY
void putchar_(char c);
/**
* An implementation of the C standard's printf/vprintf
*
* @note you must implement a @ref putchar_ function for using this function - it invokes @ref putchar_
* rather than directly performing any I/O (which insulates it from any dependence on the operating system
* and external libraries).
*
* @param format A string specifying the format of the output, with %-marked specifiers of how to interpret
* additional arguments.
* @param arg Additional arguments to the function, one for each %-specifier in @p format string
* @return The number of characters written into @p s, not counting the terminating null character
*/
///@{
PRINTF_VISIBILITY
int printf_(const char* format, ...) ATTR_PRINTF(1, 2);
PRINTF_VISIBILITY
int vprintf_(const char* format, va_list arg) ATTR_VPRINTF(1);
///@}
/**
* An implementation of the C standard's sprintf/vsprintf
*
* @note For security considerations (the potential for exceeding the buffer bounds), please consider using
* the size-constrained variant, @ref snprintf / @ref vsnprintf , instead.
*
* @param s An array in which to store the formatted string. It must be large enough to fit the formatted
* output!
* @param format A string specifying the format of the output, with %-marked specifiers of how to interpret
* additional arguments.
* @param arg Additional arguments to the function, one for each specifier in @p format
* @return The number of characters written into @p s, not counting the terminating null character
*/
///@{
PRINTF_VISIBILITY
int sprintf_(char* s, const char* format, ...) ATTR_PRINTF(2, 3);
PRINTF_VISIBILITY
int vsprintf_(char* s, const char* format, va_list arg) ATTR_VPRINTF(2);
///@}
/**
* An implementation of the C standard's snprintf/vsnprintf
*
* @param s An array in which to store the formatted string. It must be large enough to fit either the
* entire formatted output, or at least @p n characters. Alternatively, it can be NULL, in which case
* nothing will be printed, and only the number of characters which _could_ have been printed is
* tallied and returned.
* @param n The maximum number of characters to write to the array, including a terminating null character
* @param format A string specifying the format of the output, with %-marked specifiers of how to interpret
* additional arguments.
* @param arg Additional arguments to the function, one for each specifier in @p format
* @return The number of characters that COULD have been written into @p s, not counting the terminating
* null character. A value equal or larger than @p n indicates truncation. Only when the returned value
* is non-negative and less than @p n, the null-terminated string has been fully and successfully printed.
*/
///@{
PRINTF_VISIBILITY
int snprintf_(char* s, size_t count, const char* format, ...) ATTR_PRINTF(3, 4);
PRINTF_VISIBILITY
int vsnprintf_(char* s, size_t count, const char* format, va_list arg) ATTR_VPRINTF(3);
///@}
/**
* printf/vprintf with user-specified output function
*
* An alternative to @ref printf_, in which the output function is specified dynamically
* (rather than @ref putchar_ being used)
*
* @param out An output function which takes one character and a type-erased additional parameters
* @param extra_arg The type-erased argument to pass to the output function @p out with each call
* @param format A string specifying the format of the output, with %-marked specifiers of how to interpret
* additional arguments.
* @param arg Additional arguments to the function, one for each specifier in @p format
* @return The number of characters for which the output f unction was invoked, not counting the terminating null character
*
*/
PRINTF_VISIBILITY
int fctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, ...) ATTR_PRINTF(3, 4);
PRINTF_VISIBILITY
int vfctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, va_list arg) ATTR_VPRINTF(3);
#ifdef __cplusplus
} // extern "C"
#endif
#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES
# undef printf_
# undef sprintf_
# undef vsprintf_
# undef snprintf_
# undef vsnprintf_
# undef vprintf_
#endif
#endif // PRINTF_H_
printf.c
/**
* @author (c) Eyal Rozenberg <eyalroz1@gmx.com>
* 2021-2022, Haifa, Palestine/Israel
* @author (c) Marco Paland (info@paland.com)
* 2014-2019, PALANDesign Hannover, Germany
*
* @note Others have made smaller contributions to this file: see the
* contributors page at https://github.com/eyalroz/printf/graphs/contributors
* or ask one of the authors. The original code for exponential specifiers was
* contributed by Martijn Jasperse <m.jasperse@gmail.com>.
*
* @brief Small stand-alone implementation of the printf family of functions
* (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems with
* a very limited resources.
*
* @note the implementations are thread-safe; re-entrant; use no functions from
* the standard library; and do not dynamically allocate any memory.
*
* @license The MIT License (MIT)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifdef __cplusplus
#include <cstdint>
#include <climits>
extern "C" {
#else
#include <stdbool.h>
#include <stdint.h>
#include <limits.h>
#endif // __cplusplus
// Define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
// printf_config.h header file
#if PRINTF_INCLUDE_CONFIG_H
#include "printf_config.h"
#endif
#include <stdbool.h>
#include <stdint.h>
#include <printf/printf.h>
#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES
# define printf_ printf
# define sprintf_ sprintf
# define vsprintf_ vsprintf
# define snprintf_ snprintf
# define vsnprintf_ vsnprintf
# define vprintf_ vprintf
#endif
// If you have -Wfloat-equal enabled, this source code will emit a number
// of warnings for float comparisons for equality. However, within the
// context of their use in this library, these are ok to use. So we
// disable the warning for this file.
// Define PRINTF_DISABLE_WARNING_PRAGMAS to disable this block
#ifndef PRINTF_DISABLE_WARNING_PRAGMAS
#if defined(__GNUC__) || defined (__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfloat-equal"
#endif // __GNUC__ || defined (__clang__)
#endif // PRINTF_DISABLE_WARNING_PRAGMAS
// 'ntoa' conversion buffer size, this must be big enough to hold one converted
// numeric number including padded zeros (dynamically created on stack)
#ifndef PRINTF_INTEGER_BUFFER_SIZE
#define PRINTF_INTEGER_BUFFER_SIZE 32
#endif
// size of the fixed (on-stack) buffer for printing individual decimal numbers.
// this must be big enough to hold one converted floating-point value including
// padded zeros.
#ifndef PRINTF_DECIMAL_BUFFER_SIZE
#define PRINTF_DECIMAL_BUFFER_SIZE 32
#endif
// Support for the decimal notation floating point conversion specifiers (%f, %F)
#ifndef PRINTF_SUPPORT_DECIMAL_SPECIFIERS
#define PRINTF_SUPPORT_DECIMAL_SPECIFIERS 1
#endif
// Support for the exponential notation floating point conversion specifiers (%e, %g, %E, %G)
#ifndef PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
#define PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 1
#endif
// Support for the length write-back specifier (%n)
#ifndef PRINTF_SUPPORT_WRITEBACK_SPECIFIER
#define PRINTF_SUPPORT_WRITEBACK_SPECIFIER 1
#endif
// Default precision for the floating point conversion specifiers (the C standard sets this at 6)
#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
#define PRINTF_DEFAULT_FLOAT_PRECISION 6
#endif
// According to the C languages standard, printf() and related functions must be able to print any
// integral number in floating-point notation, regardless of length, when using the %f specifier -
// possibly hundreds of characters, potentially overflowing your buffers. In this implementation,
// all values beyond this threshold are switched to exponential notation.
#ifndef PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL
#define PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL 9
#endif
// Support for the long long integral types (with the ll, z and t length modifiers for specifiers
// %d,%i,%o,%x,%X,%u, and with the %p specifier). Note: 'L' (long double) is not supported.
#ifndef PRINTF_SUPPORT_LONG_LONG
#define PRINTF_SUPPORT_LONG_LONG 1
#endif
// The number of terms in a Taylor series expansion of log_10(x) to
// use for approximation - including the power-zero term (i.e. the
// value at the point of expansion).
#ifndef PRINTF_LOG10_TAYLOR_TERMS
#define PRINTF_LOG10_TAYLOR_TERMS 4
#endif
#if PRINTF_LOG10_TAYLOR_TERMS <= 1
#error "At least one non-constant Taylor expansion is necessary for the log10() calculation"
#endif
#define PRINTF_PREFER_DECIMAL false
#define PRINTF_PREFER_EXPONENTIAL true
///////////////////////////////////////////////////////////////////////////////
// The following will convert the number-of-digits into an exponential-notation literal
#define PRINTF_CONCATENATE(s1, s2) s1##s2
#define PRINTF_EXPAND_THEN_CONCATENATE(s1, s2) PRINTF_CONCATENATE(s1, s2)
#define PRINTF_FLOAT_NOTATION_THRESHOLD PRINTF_EXPAND_THEN_CONCATENATE(1e,PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL)
// internal flag definitions
#define FLAGS_ZEROPAD (1U << 0U)
#define FLAGS_LEFT (1U << 1U)
#define FLAGS_PLUS (1U << 2U)
#define FLAGS_SPACE (1U << 3U)
#define FLAGS_HASH (1U << 4U)
#define FLAGS_UPPERCASE (1U << 5U)
#define FLAGS_CHAR (1U << 6U)
#define FLAGS_SHORT (1U << 7U)
#define FLAGS_INT (1U << 8U)
// Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
#define FLAGS_LONG (1U << 9U)
#define FLAGS_LONG_LONG (1U << 10U)
#define FLAGS_PRECISION (1U << 11U)
#define FLAGS_ADAPT_EXP (1U << 12U)
#define FLAGS_POINTER (1U << 13U)
// Note: Similar, but not identical, effect as FLAGS_HASH
#define FLAGS_SIGNED (1U << 14U)
// Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
#define FLAGS_INT8 FLAGS_CHAR
#if (SHRT_MAX == 32767LL)
#define FLAGS_INT16 FLAGS_SHORT
#elif (INT_MAX == 32767LL)
#define FLAGS_INT16 FLAGS_INT
#elif (LONG_MAX == 32767LL)
#define FLAGS_INT16 FLAGS_LONG
#elif (LLONG_MAX == 32767LL)
#define FLAGS_INT16 FLAGS_LONG_LONG
#else
#error "No basic integer type has a size of 16 bits exactly"
#endif
#if (SHRT_MAX == 2147483647LL)
#define FLAGS_INT32 FLAGS_SHORT
#elif (INT_MAX == 2147483647LL)
#define FLAGS_INT32 FLAGS_INT
#elif (LONG_MAX == 2147483647LL)
#define FLAGS_INT32 FLAGS_LONG
#elif (LLONG_MAX == 2147483647LL)
#define FLAGS_INT32 FLAGS_LONG_LONG
#else
#error "No basic integer type has a size of 32 bits exactly"
#endif
#if (SHRT_MAX == 9223372036854775807LL)
#define FLAGS_INT64 FLAGS_SHORT
#elif (INT_MAX == 9223372036854775807LL)
#define FLAGS_INT64 FLAGS_INT
#elif (LONG_MAX == 9223372036854775807LL)
#define FLAGS_INT64 FLAGS_LONG
#elif (LLONG_MAX == 9223372036854775807LL)
#define FLAGS_INT64 FLAGS_LONG_LONG
#else
#error "No basic integer type has a size of 64 bits exactly"
#endif
#endif // PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
typedef unsigned int printf_flags_t;
#define BASE_BINARY 2
#define BASE_OCTAL 8
#define BASE_DECIMAL 10
#define BASE_HEX 16
typedef uint8_t numeric_base_t;
#if PRINTF_SUPPORT_LONG_LONG
typedef unsigned long long printf_unsigned_value_t;
typedef long long printf_signed_value_t;
#else
typedef unsigned long printf_unsigned_value_t;
typedef long printf_signed_value_t;
#endif
// The printf()-family functions return an `int`; it is therefore
// unnecessary/inappropriate to use size_t - often larger than int
// in practice - for non-negative related values, such as widths,
// precisions, offsets into buffers used for printing and the sizes
// of these buffers. instead, we use:
typedef unsigned int printf_size_t;
#define PRINTF_MAX_POSSIBLE_BUFFER_SIZE INT_MAX
// If we were to nitpick, this would actually be INT_MAX + 1,
// since INT_MAX is the maximum return value, which excludes the
// trailing '\0'.
#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS)
#include <float.h>
#if FLT_RADIX != 2
#error "Non-binary-radix floating-point types are unsupported."
#endif
#if DBL_MANT_DIG == 24
#define DOUBLE_SIZE_IN_BITS 32
typedef uint32_t double_uint_t;
#define DOUBLE_EXPONENT_MASK 0xFFU
#define DOUBLE_BASE_EXPONENT 127
#define DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10 -38
#define DOUBLE_MAX_SUBNORMAL_POWER_OF_10 1e-38
#elif DBL_MANT_DIG == 53
#define DOUBLE_SIZE_IN_BITS 64
typedef uint64_t double_uint_t;
#define DOUBLE_EXPONENT_MASK 0x7FFU
#define DOUBLE_BASE_EXPONENT 1023
#define DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10 -308
#define DOUBLE_MAX_SUBNORMAL_POWER_OF_10 1e-308
#else
#error "Unsupported double type configuration"
#endif
#define DOUBLE_STORED_MANTISSA_BITS (DBL_MANT_DIG - 1)
typedef union {
double_uint_t U;
double F;
} double_with_bit_access;
// This is unnecessary in C99, since compound initializers can be used,
// but:
// 1. Some compilers are finicky about this;
// 2. Some people may want to convert this to C89;
// 3. If you try to use it as C++, only C++20 supports compound literals
static inline double_with_bit_access get_bit_access(double x)
{
double_with_bit_access dwba;
dwba.F = x;
return dwba;
}
static inline int get_sign_bit(double x)
{
// The sign is stored in the highest bit
return (int) (get_bit_access(x).U >> (DOUBLE_SIZE_IN_BITS - 1));
}
static inline int get_exp2(double_with_bit_access x)
{
// The exponent in an IEEE-754 floating-point number occupies a contiguous
// sequence of bits (e.g. 52..62 for 64-bit doubles), but with a non-trivial representation: An
// unsigned offset from some negative value (with the extremal offset values reserved for
// special use).
return (int)((x.U >> DOUBLE_STORED_MANTISSA_BITS ) & DOUBLE_EXPONENT_MASK) - DOUBLE_BASE_EXPONENT;
}
#define PRINTF_ABS(_x) ( (_x) > 0 ? (_x) : -(_x) )
#endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS)
// Note in particular the behavior here on LONG_MIN or LLONG_MIN; it is valid
// and well-defined, but if you're not careful you can easily trigger undefined
// behavior with -LONG_MIN or -LLONG_MIN
#define ABS_FOR_PRINTING(_x) ((printf_unsigned_value_t) ( (_x) > 0 ? (_x) : -((printf_signed_value_t)_x) ))
// wrapper (used as buffer) for output function type
//
// One of the following must hold:
// 1. max_chars is 0
// 2. buffer is non-null
// 3. function is non-null
//
// ... otherwise bad things will happen.
typedef struct {
void (*function)(char c, void* extra_arg);
void* extra_function_arg;
char* buffer;
printf_size_t pos;
printf_size_t max_chars;
} output_gadget_t;
// Note: This function currently assumes it is not passed a '\0' c,
// or alternatively, that '\0' can be passed to the function in the output
// gadget. The former assumption holds within the printf library. It also
// assumes that the output gadget has been properly initialized.
static inline void putchar_via_gadget(output_gadget_t* gadget, char c)
{
printf_size_t write_pos = gadget->pos++;
// We're _always_ increasing pos, so as to count how may characters
// _would_ have been written if not for the max_chars limitation
if (write_pos >= gadget->max_chars) {
return;
}
if (gadget->function != NULL) {
// No check for c == '\0' .
gadget->function(c, gadget->extra_function_arg);
}
else {
// it must be the case that gadget->buffer != NULL , due to the constraint
// on output_gadget_t ; and note we're relying on write_pos being non-negative.
gadget->buffer[write_pos] = c;
}
}
// Possibly-write the string-terminating '\0' character
static inline void append_termination_with_gadget(output_gadget_t* gadget)
{
if (gadget->function != NULL || gadget->max_chars == 0) {
return;
}
if (gadget->buffer == NULL) {
return;
}
printf_size_t null_char_pos = gadget->pos < gadget->max_chars ? gadget->pos : gadget->max_chars - 1;
gadget->buffer[null_char_pos] = '\0';
}
// We can't use putchar_ as is, since our output gadget
// only takes pointers to functions with an extra argument
static inline void putchar_wrapper(char c, void* unused)
{
(void) unused;
putchar_(c);
}
static inline output_gadget_t discarding_gadget()
{
output_gadget_t gadget;
gadget.function = NULL;
gadget.extra_function_arg = NULL;
gadget.buffer = NULL;
gadget.pos = 0;
gadget.max_chars = 0;
return gadget;
}
static inline output_gadget_t buffer_gadget(char* buffer, size_t buffer_size)
{
printf_size_t usable_buffer_size = (buffer_size > PRINTF_MAX_POSSIBLE_BUFFER_SIZE) ?
PRINTF_MAX_POSSIBLE_BUFFER_SIZE : (printf_size_t) buffer_size;
output_gadget_t result = discarding_gadget();
if (buffer != NULL) {
result.buffer = buffer;
result.max_chars = usable_buffer_size;
}
return result;
}
static inline output_gadget_t function_gadget(void (*function)(char, void*), void* extra_arg)
{
output_gadget_t result = discarding_gadget();
result.function = function;
result.extra_function_arg = extra_arg;
result.max_chars = PRINTF_MAX_POSSIBLE_BUFFER_SIZE;
return result;
}
static inline output_gadget_t extern_putchar_gadget()
{
return function_gadget(putchar_wrapper, NULL);
}
// internal secure strlen
// @return The length of the string (excluding the terminating 0) limited by 'maxsize'
// @note strlen uses size_t, but wes only use this function with printf_size_t
// variables - hence the signature.
static inline printf_size_t strnlen_s_(const char* str, printf_size_t maxsize)
{
const char* s;
for (s = str; *s && maxsize--; ++s);
return (printf_size_t)(s - str);
}
// internal test if char is a digit (0-9)
// @return true if char is a digit
static inline bool is_digit_(char ch)
{
return (ch >= '0') && (ch <= '9');
}
// internal ASCII string to printf_size_t conversion
static printf_size_t atou_(const char** str)
{
printf_size_t i = 0U;
while (is_digit_(**str)) {
i = i * 10U + (printf_size_t)(*((*str)++) - '0');
}
return i;
}
// output the specified string in reverse, taking care of any zero-padding
static void out_rev_(output_gadget_t* output, const char* buf, printf_size_t len, printf_size_t width, printf_flags_t flags)
{
const printf_size_t start_pos = output->pos;
// pad spaces up to given width
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
for (printf_size_t i = len; i < width; i++) {
putchar_via_gadget(output, ' ');
}
}
// reverse string
while (len) {
putchar_via_gadget(output, buf[--len]);
}
// append pad spaces up to given width
if (flags & FLAGS_LEFT) {
while (output->pos - start_pos < width) {
putchar_via_gadget(output, ' ');
}
}
}
// Invoked by print_integer after the actual number has been printed, performing necessary
// work on the number's prefix (as the number is initially printed in reverse order)
static void print_integer_finalization(output_gadget_t* output, char* buf, printf_size_t len, bool negative, numeric_base_t base, printf_size_t precision, printf_size_t width, printf_flags_t flags)
{
printf_size_t unpadded_len = len;
// pad with leading zeros
{
if (!(flags & FLAGS_LEFT)) {
if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
width--;
}
while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_INTEGER_BUFFER_SIZE)) {
buf[len++] = '0';
}
}
while ((len < precision) && (len < PRINTF_INTEGER_BUFFER_SIZE)) {
buf[len++] = '0';
}
if (base == BASE_OCTAL && (len > unpadded_len)) {
// Since we've written some zeros, we've satisfied the alternative format leading space requirement
flags &= ~FLAGS_HASH;
}
}
// handle hash
if (flags & (FLAGS_HASH | FLAGS_POINTER)) {
if (!(flags & FLAGS_PRECISION) && len && ((len == precision) || (len == width))) {
// Let's take back some padding digits to fit in what will eventually
// be the format-specific prefix
if (unpadded_len < len) {
len--; // This should suffice for BASE_OCTAL
}
if (len && (base == BASE_HEX || base == BASE_BINARY) && (unpadded_len < len)) {
len--; // ... and an extra one for 0x or 0b
}
}
if ((base == BASE_HEX) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_INTEGER_BUFFER_SIZE)) {
buf[len++] = 'x';
}
else if ((base == BASE_HEX) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_INTEGER_BUFFER_SIZE)) {
buf[len++] = 'X';
}
else if ((base == BASE_BINARY) && (len < PRINTF_INTEGER_BUFFER_SIZE)) {
buf[len++] = 'b';
}
if (len < PRINTF_INTEGER_BUFFER_SIZE) {
buf[len++] = '0';
}
}
if (len < PRINTF_INTEGER_BUFFER_SIZE) {
if (negative) {
buf[len++] = '-';
}
else if (flags & FLAGS_PLUS) {
buf[len++] = '+'; // ignore the space if the '+' exists
}
else if (flags & FLAGS_SPACE) {
buf[len++] = ' ';
}
}
out_rev_(output, buf, len, width, flags);
}
// An internal itoa-like function
static void print_integer(output_gadget_t* output, printf_unsigned_value_t value, bool negative, numeric_base_t base, printf_size_t precision, printf_size_t width, printf_flags_t flags)
{
char buf[PRINTF_INTEGER_BUFFER_SIZE];
printf_size_t len = 0U;
if (!value) {
if ( !(flags & FLAGS_PRECISION) ) {
buf[len++] = '0';
flags &= ~FLAGS_HASH;
// We drop this flag this since either the alternative and regular modes of the specifier
// don't differ on 0 values, or (in the case of octal) we've already provided the special
// handling for this mode.
}
else if (base == BASE_HEX) {
flags &= ~FLAGS_HASH;
// We drop this flag this since either the alternative and regular modes of the specifier
// don't differ on 0 values
}
}
else {
do {
const char digit = (char)(value % base);
buf[len++] = (char)(digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10);
value /= base;
} while (value && (len < PRINTF_INTEGER_BUFFER_SIZE));
}
print_integer_finalization(output, buf, len, negative, base, precision, width, flags);
}
#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS)
// Stores a fixed-precision representation of a double relative
// to a fixed precision (which cannot be determined by examining this structure)
struct double_components {
int_fast64_t integral;
int_fast64_t fractional;
// ... truncation of the actual fractional part of the double value, scaled
// by the precision value
bool is_negative;
};
#define NUM_DECIMAL_DIGITS_IN_INT64_T 18
#define PRINTF_MAX_PRECOMPUTED_POWER_OF_10 NUM_DECIMAL_DIGITS_IN_INT64_T
static const double powers_of_10[NUM_DECIMAL_DIGITS_IN_INT64_T] = {
1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08,
1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17
};
#define PRINTF_MAX_SUPPORTED_PRECISION NUM_DECIMAL_DIGITS_IN_INT64_T - 1
// Break up a double number - which is known to be a finite non-negative number -
// into its base-10 parts: integral - before the decimal point, and fractional - after it.
// Taken the precision into account, but does not change it even internally.
static struct double_components get_components(double number, printf_size_t precision)
{
struct double_components number_;
number_.is_negative = get_sign_bit(number);
double abs_number = (number_.is_negative) ? -number : number;
number_.integral = (int_fast64_t)abs_number;
double remainder = (abs_number - (double) number_.integral) * powers_of_10[precision];
number_.fractional = (int_fast64_t)remainder;
remainder -= (double) number_.fractional;
if (remainder > 0.5) {
++number_.fractional;
// handle rollover, e.g. case 0.99 with precision 1 is 1.0
if ((double) number_.fractional >= powers_of_10[precision]) {
number_.fractional = 0;
++number_.integral;
}
}
else if ((remainder == 0.5) && ((number_.fractional == 0U) || (number_.fractional & 1U))) {
// if halfway, round up if odd OR if last digit is 0
++number_.fractional;
}
if (precision == 0U) {
remainder = abs_number - (double) number_.integral;
if ((!(remainder < 0.5) || (remainder > 0.5)) && (number_.integral & 1)) {
// exactly 0.5 and ODD, then round up
// 1.5 -> 2, but 2.5 -> 2
++number_.integral;
}
}
return number_;
}
#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
struct scaling_factor {
double raw_factor;
bool multiply; // if true, need to multiply by raw_factor; otherwise need to divide by it
};
static double apply_scaling(double num, struct scaling_factor normalization)
{
return normalization.multiply ? num * normalization.raw_factor : num / normalization.raw_factor;
}
static double unapply_scaling(double normalized, struct scaling_factor normalization)
{
return normalization.multiply ? normalized / normalization.raw_factor : normalized * normalization.raw_factor;
}
static struct scaling_factor update_normalization(struct scaling_factor sf, double extra_multiplicative_factor)
{
struct scaling_factor result;
if (sf.multiply) {
result.multiply = true;
result.raw_factor = sf.raw_factor * extra_multiplicative_factor;
}
else {
int factor_exp2 = get_exp2(get_bit_access(sf.raw_factor));
int extra_factor_exp2 = get_exp2(get_bit_access(extra_multiplicative_factor));
// Divide the larger-exponent raw raw_factor by the smaller
if (PRINTF_ABS(factor_exp2) > PRINTF_ABS(extra_factor_exp2)) {
result.multiply = false;
result.raw_factor = sf.raw_factor / extra_multiplicative_factor;
}
else {
result.multiply = true;
result.raw_factor = extra_multiplicative_factor / sf.raw_factor;
}
}
return result;
}
static struct double_components get_normalized_components(bool negative, printf_size_t precision, double non_normalized, struct scaling_factor normalization, int floored_exp10)
{
struct double_components components;
components.is_negative = negative;
double scaled = apply_scaling(non_normalized, normalization);
bool close_to_representation_extremum = ( (-floored_exp10 + (int) precision) >= DBL_MAX_10_EXP - 1 );
if (close_to_representation_extremum) {
// We can't have a normalization factor which also accounts for the precision, i.e. moves
// some decimal digits into the mantissa, since it's unrepresentable, or nearly unrepresentable.
// So, we'll give up early on getting extra precision...
return get_components(negative ? -scaled : scaled, precision);
}
components.integral = (int_fast64_t) scaled;
double remainder = non_normalized - unapply_scaling((double) components.integral, normalization);
double prec_power_of_10 = powers_of_10[precision];
struct scaling_factor account_for_precision = update_normalization(normalization, prec_power_of_10);
double scaled_remainder = apply_scaling(remainder, account_for_precision);
double rounding_threshold = 0.5;
components.fractional = (int_fast64_t) scaled_remainder; // when precision == 0, the assigned value should be 0
scaled_remainder -= (double) components.fractional; //when precision == 0, this will not change scaled_remainder
components.fractional += (scaled_remainder >= rounding_threshold);
if (scaled_remainder == rounding_threshold) {
// banker's rounding: Round towards the even number (making the mean error 0)
components.fractional &= ~((int_fast64_t) 0x1);
}
// handle rollover, e.g. the case of 0.99 with precision 1 becoming (0,100),
// and must then be corrected into (1, 0).
// Note: for precision = 0, this will "translate" the rounding effect from
// the fractional part to the integral part where it should actually be
// felt (as prec_power_of_10 is 1)
if ((double) components.fractional >= prec_power_of_10) {
components.fractional = 0;
++components.integral;
}
return components;
}
#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
static void print_broken_up_decimal(
struct double_components number_, output_gadget_t* output, printf_size_t precision,
printf_size_t width, printf_flags_t flags, char *buf, printf_size_t len)
{
if (precision != 0U) {
// do fractional part, as an unsigned number
printf_size_t count = precision;
// %g/%G mandates we skip the trailing 0 digits...
if ((flags & FLAGS_ADAPT_EXP) && !(flags & FLAGS_HASH) && (number_.fractional > 0)) {
while(true) {
int_fast64_t digit = number_.fractional % 10U;
if (digit != 0) {
break;
}
--count;
number_.fractional /= 10U;
}
// ... and even the decimal point if there are no
// non-zero fractional part digits (see below)
}
if (number_.fractional > 0 || !(flags & FLAGS_ADAPT_EXP) || (flags & FLAGS_HASH) ) {
while (len < PRINTF_DECIMAL_BUFFER_SIZE) {
--count;
buf[len++] = (char)('0' + number_.fractional % 10U);
if (!(number_.fractional /= 10U)) {
break;
}
}
// add extra 0s
while ((len < PRINTF_DECIMAL_BUFFER_SIZE) && (count > 0U)) {
buf[len++] = '0';
--count;
}
if (len < PRINTF_DECIMAL_BUFFER_SIZE) {
buf[len++] = '.';
}
}
}
else {
if ((flags & FLAGS_HASH) && (len < PRINTF_DECIMAL_BUFFER_SIZE)) {
buf[len++] = '.';
}
}
// Write the integer part of the number (it comes after the fractional
// since the character order is reversed)
while (len < PRINTF_DECIMAL_BUFFER_SIZE) {
buf[len++] = (char)('0' + (number_.integral % 10));
if (!(number_.integral /= 10)) {
break;
}
}
// pad leading zeros
if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
if (width && (number_.is_negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
width--;
}
while ((len < width) && (len < PRINTF_DECIMAL_BUFFER_SIZE)) {
buf[len++] = '0';
}
}
if (len < PRINTF_DECIMAL_BUFFER_SIZE) {
if (number_.is_negative) {
buf[len++] = '-';
}
else if (flags & FLAGS_PLUS) {
buf[len++] = '+'; // ignore the space if the '+' exists
}
else if (flags & FLAGS_SPACE) {
buf[len++] = ' ';
}
}
out_rev_(output, buf, len, width, flags);
}
// internal ftoa for fixed decimal floating point
static void print_decimal_number(output_gadget_t* output, double number, printf_size_t precision, printf_size_t width, printf_flags_t flags, char* buf, printf_size_t len)
{
struct double_components value_ = get_components(number, precision);
print_broken_up_decimal(value_, output, precision, width, flags, buf, len);
}
#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
// A floor function - but one which only works for numbers whose
// floor value is representable by an int.
static int bastardized_floor(double x)
{
if (x >= 0) { return (int) x; }
int n = (int) x;
return ( ((double) n) == x ) ? n : n-1;
}
// Computes the base-10 logarithm of the input number - which must be an actual
// positive number (not infinity or NaN, nor a sub-normal)
static double log10_of_positive(double positive_number)
{
// The implementation follows David Gay (https://www.ampl.com/netlib/fp/dtoa.c).
//
// Since log_10 ( M * 2^x ) = log_10(M) + x , we can separate the components of
// our input number, and need only solve log_10(M) for M between 1 and 2 (as
// the base-2 mantissa is always 1-point-something). In that limited range, a
// Taylor series expansion of log10(x) should serve us well enough; and we'll
// take the mid-point, 1.5, as the point of expansion.
double_with_bit_access dwba = get_bit_access(positive_number);
// based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
int exp2 = get_exp2(dwba);
// drop the exponent, so dwba.F comes into the range [1,2)
dwba.U = (dwba.U & (((double_uint_t) (1) << DOUBLE_STORED_MANTISSA_BITS) - 1U)) |
((double_uint_t) DOUBLE_BASE_EXPONENT << DOUBLE_STORED_MANTISSA_BITS);
double z = (dwba.F - 1.5);
return (
// Taylor expansion around 1.5:
0.1760912590556812420 // Expansion term 0: ln(1.5) / ln(10)
+ z * 0.2895296546021678851 // Expansion term 1: (M - 1.5) * 2/3 / ln(10)
#if PRINTF_LOG10_TAYLOR_TERMS > 2
- z*z * 0.0965098848673892950 // Expansion term 2: (M - 1.5)^2 * 2/9 / ln(10)
#if PRINTF_LOG10_TAYLOR_TERMS > 3
+ z*z*z * 0.0428932821632841311 // Expansion term 2: (M - 1.5)^3 * 8/81 / ln(10)
#endif
#endif
// exact log_2 of the exponent x, with logarithm base change
+ exp2 * 0.30102999566398119521 // = exp2 * log_10(2) = exp2 * ln(2)/ln(10)
);
}
static double pow10_of_int(int floored_exp10)
{
// A crude hack for avoiding undesired behavior with barely-normal or slightly-subnormal values.
if (floored_exp10 == DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10) {
return DOUBLE_MAX_SUBNORMAL_POWER_OF_10;
}
// Compute 10^(floored_exp10) but (try to) make sure that doesn't overflow
double_with_bit_access dwba;
int exp2 = bastardized_floor(floored_exp10 * 3.321928094887362 + 0.5);
const double z = floored_exp10 * 2.302585092994046 - exp2 * 0.6931471805599453;
const double z2 = z * z;
dwba.U = ((double_uint_t)(exp2) + DOUBLE_BASE_EXPONENT) << DOUBLE_STORED_MANTISSA_BITS;
// compute exp(z) using continued fractions,
// see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
dwba.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
return dwba.F;
}
static void print_exponential_number(output_gadget_t* output, double number, printf_size_t precision, printf_size_t width, printf_flags_t flags, char* buf, printf_size_t len)
{
const bool negative = get_sign_bit(number);
// This number will decrease gradually (by factors of 10) as we "extract" the exponent out of it
double abs_number = negative ? -number : number;
int floored_exp10;
bool abs_exp10_covered_by_powers_table;
struct scaling_factor normalization;
// Determine the decimal exponent
if (abs_number == 0.0) {
// TODO: This is a special-case for 0.0 (and -0.0); but proper handling is required for denormals more generally.
floored_exp10 = 0; // ... and no need to set a normalization factor or check the powers table
}
else {
double exp10 = log10_of_positive(abs_number);
floored_exp10 = bastardized_floor(exp10);
double p10 = pow10_of_int(floored_exp10);
// correct for rounding errors
if (abs_number < p10) {
floored_exp10--;
p10 /= 10;
}
abs_exp10_covered_by_powers_table = PRINTF_ABS(floored_exp10) < PRINTF_MAX_PRECOMPUTED_POWER_OF_10;
normalization.raw_factor = abs_exp10_covered_by_powers_table ? powers_of_10[PRINTF_ABS(floored_exp10)] : p10;
}
// We now begin accounting for the widths of the two parts of our printed field:
// the decimal part after decimal exponent extraction, and the base-10 exponent part.
// For both of these, the value of 0 has a special meaning, but not the same one:
// a 0 exponent-part width means "don't print the exponent"; a 0 decimal-part width
// means "use as many characters as necessary".
bool fall_back_to_decimal_only_mode = false;
if (flags & FLAGS_ADAPT_EXP) {
int required_significant_digits = (precision == 0) ? 1 : (int) precision;
// Should we want to fall-back to "%f" mode, and only print the decimal part?
fall_back_to_decimal_only_mode = (floored_exp10 >= -4 && floored_exp10 < required_significant_digits);
// Now, let's adjust the precision
// This also decided how we adjust the precision value - as in "%g" mode,
// "precision" is the number of _significant digits_, and this is when we "translate"
// the precision value to an actual number of decimal digits.
int precision_ = fall_back_to_decimal_only_mode ?
(int) precision - 1 - floored_exp10 :
(int) precision - 1; // the presence of the exponent ensures only one significant digit comes before the decimal point
precision = (precision_ > 0 ? (unsigned) precision_ : 0U);
flags |= FLAGS_PRECISION; // make sure print_broken_up_decimal respects our choice above
}
normalization.multiply = (floored_exp10 < 0 && abs_exp10_covered_by_powers_table);
bool should_skip_normalization = (fall_back_to_decimal_only_mode || floored_exp10 == 0);
struct double_components decimal_part_components =
should_skip_normalization ?
get_components(negative ? -abs_number : abs_number, precision) :
get_normalized_components(negative, precision, abs_number, normalization, floored_exp10);
// Account for roll-over, e.g. rounding from 9.99 to 100.0 - which effects
// the exponent and may require additional tweaking of the parts
if (fall_back_to_decimal_only_mode) {
if ((flags & FLAGS_ADAPT_EXP) && floored_exp10 >= -1 && ((double)decimal_part_components.integral == powers_of_10[floored_exp10 + 1])) {
floored_exp10++; // Not strictly necessary, since floored_exp10 is no longer really used
precision--;
// ... and it should already be the case that decimal_part_components.fractional == 0
}
// TODO: What about rollover strictly within the fractional part?
}
else {
if (decimal_part_components.integral >= 10) {
floored_exp10++;
decimal_part_components.integral = 1;
decimal_part_components.fractional = 0;
}
}
// the floored_exp10 format is "E%+03d" and largest possible floored_exp10 value for a 64-bit double
// is "307" (for 2^1023), so we set aside 4-5 characters overall
printf_size_t exp10_part_width = fall_back_to_decimal_only_mode ? 0U : (PRINTF_ABS(floored_exp10) < 100) ? 4U : 5U;
printf_size_t decimal_part_width =
((flags & FLAGS_LEFT) && exp10_part_width) ?
// We're padding on the right, so the width constraint is the exponent part's
// problem, not the decimal part's, so we'll use as many characters as we need:
0U :
// We're padding on the left; so the width constraint is the decimal part's
// problem. Well, can both the decimal part and the exponent part fit within our overall width?
((width > exp10_part_width) ?
// Yes, so we limit our decimal part's width.
// (Note this is trivially valid even if we've fallen back to "%f" mode)
width - exp10_part_width :
// No; we just give up on any restriction on the decimal part and use as many
// characters as we need
0U);
const printf_size_t printed_exponential_start_pos = output->pos;
print_broken_up_decimal(decimal_part_components, output, precision, decimal_part_width, flags, buf, len);
if (! fall_back_to_decimal_only_mode) {
putchar_via_gadget(output, (flags & FLAGS_UPPERCASE) ? 'E' : 'e');
print_integer(output,
ABS_FOR_PRINTING(floored_exp10),
floored_exp10 < 0, 10, 0, exp10_part_width - 1,
FLAGS_ZEROPAD | FLAGS_PLUS);
if (flags & FLAGS_LEFT) {
// We need to right-pad with spaces to meet the width requirement
while (output->pos - printed_exponential_start_pos < width) {
putchar_via_gadget(output, ' ');
}
}
}
}
#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
static void print_floating_point(output_gadget_t* output, double value, printf_size_t precision, printf_size_t width, printf_flags_t flags, bool prefer_exponential)
{
char buf[PRINTF_DECIMAL_BUFFER_SIZE];
printf_size_t len = 0U;
// test for special values
if (value != value) {
out_rev_(output, "nan", 3, width, flags);
return;
}
if (value < -DBL_MAX) {
out_rev_(output, "fni-", 4, width, flags);
return;
}
if (value > DBL_MAX) {
out_rev_(output, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
return;
}
if (!prefer_exponential &&
((value > PRINTF_FLOAT_NOTATION_THRESHOLD) || (value < -PRINTF_FLOAT_NOTATION_THRESHOLD))) {
// The required behavior of standard printf is to print _every_ integral-part digit -- which could mean
// printing hundreds of characters, overflowing any fixed internal buffer and necessitating a more complicated
// implementation.
#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
print_exponential_number(output, value, precision, width, flags, buf, len);
#endif
return;
}
// set default precision, if not set explicitly
if (!(flags & FLAGS_PRECISION)) {
precision = PRINTF_DEFAULT_FLOAT_PRECISION;
}
// limit precision so that our integer holding the fractional part does not overflow
while ((len < PRINTF_DECIMAL_BUFFER_SIZE) && (precision > PRINTF_MAX_SUPPORTED_PRECISION)) {
buf[len++] = '0'; // This respects the precision in terms of result length only
precision--;
}
#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
if (prefer_exponential)
print_exponential_number(output, value, precision, width, flags, buf, len);
else
#endif
print_decimal_number(output, value, precision, width, flags, buf, len);
}
#endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS)
// Advances the format pointer past the flags, and returns the parsed flags
// due to the characters passed
static printf_flags_t parse_flags(const char** format)
{
printf_flags_t flags = 0U;
do {
switch (**format) {
case '0': flags |= FLAGS_ZEROPAD; (*format)++; break;
case '-': flags |= FLAGS_LEFT; (*format)++; break;
case '+': flags |= FLAGS_PLUS; (*format)++; break;
case ' ': flags |= FLAGS_SPACE; (*format)++; break;
case '#': flags |= FLAGS_HASH; (*format)++; break;
default : return flags;
}
} while (true);
}
// internal vsnprintf - used for implementing _all library functions
// Note: We don't like the C standard's parameter names, so using more informative parameter names
// here instead.
static int _vsnprintf(output_gadget_t* output, const char* format, va_list args)
{
// Note: The library only calls _vsnprintf() with output->pos being 0. However, it is
// possible to call this function with a non-zero pos value for some "remedial printing".
while (*format)
{
// format specifier? %[flags][width][.precision][length]
if (*format != '%') {
// no
putchar_via_gadget(output, *format);
format++;
continue;
}
else {
// yes, evaluate it
format++;
}
printf_flags_t flags = parse_flags(&format);
// evaluate width field
printf_size_t width = 0U;
if (is_digit_(*format)) {
width = (printf_size_t) atou_(&format);
}
else if (*format == '*') {
const int w = va_arg(args, int);
if (w < 0) {
flags |= FLAGS_LEFT; // reverse padding
width = (printf_size_t)-w;
}
else {
width = (printf_size_t)w;
}
format++;
}
// evaluate precision field
printf_size_t precision = 0U;
if (*format == '.') {
flags |= FLAGS_PRECISION;
format++;
if (is_digit_(*format)) {
precision = (printf_size_t) atou_(&format);
}
else if (*format == '*') {
const int precision_ = va_arg(args, int);
precision = precision_ > 0 ? (printf_size_t) precision_ : 0U;
format++;
}
}
// evaluate length field
switch (*format) {
#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
case 'I' : {
format++;
// Greedily parse for size in bits: 8, 16, 32 or 64
switch(*format) {
case '8': flags |= FLAGS_INT8;
format++;
break;
case '1':
format++;
if (*format == '6') { format++; flags |= FLAGS_INT16; }
break;
case '3':
format++;
if (*format == '2') { format++; flags |= FLAGS_INT32; }
break;
case '6':
format++;
if (*format == '4') { format++; flags |= FLAGS_INT64; }
break;
default: break;
}
break;
}
#endif
case 'l' :
flags |= FLAGS_LONG;
format++;
if (*format == 'l') {
flags |= FLAGS_LONG_LONG;
format++;
}
break;
case 'h' :
flags |= FLAGS_SHORT;
format++;
if (*format == 'h') {
flags |= FLAGS_CHAR;
format++;
}
break;
case 't' :
flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
case 'j' :
flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
case 'z' :
flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
default:
break;
}
// evaluate specifier
switch (*format) {
case 'd' :
case 'i' :
case 'u' :
case 'x' :
case 'X' :
case 'o' :
case 'b' : {
if (*format == 'd' || *format == 'i') {
flags |= FLAGS_SIGNED;
}
numeric_base_t base;
if (*format == 'x' || *format == 'X') {
base = BASE_HEX;
}
else if (*format == 'o') {
base = BASE_OCTAL;
}
else if (*format == 'b') {
base = BASE_BINARY;
}
else {
base = BASE_DECIMAL;
flags &= ~FLAGS_HASH; // decimal integers have no alternative presentation
}
if (*format == 'X') {
flags |= FLAGS_UPPERCASE;
}
format++;
// ignore '0' flag when precision is given
if (flags & FLAGS_PRECISION) {
flags &= ~FLAGS_ZEROPAD;
}
if (flags & FLAGS_SIGNED) {
// A signed specifier: d, i or possibly I + bit size if enabled
if (flags & FLAGS_LONG_LONG) {
#if PRINTF_SUPPORT_LONG_LONG
const long long value = va_arg(args, long long);
print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags);
#endif
}
else if (flags & FLAGS_LONG) {
const long value = va_arg(args, long);
print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags);
}
else {
// We never try to interpret the argument as something potentially-smaller than int,
// due to integer promotion rules: Even if the user passed a short int, short unsigned
// etc. - these will come in after promotion, as int's (or unsigned for the case of
// short unsigned when it has the same size as int)
const int value =
(flags & FLAGS_CHAR) ? (signed char) va_arg(args, int) :
(flags & FLAGS_SHORT) ? (short int) va_arg(args, int) :
va_arg(args, int);
print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags);
}
}
else {
// An unsigned specifier: u, x, X, o, b
flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
if (flags & FLAGS_LONG_LONG) {
#if PRINTF_SUPPORT_LONG_LONG
print_integer(output, (printf_unsigned_value_t) va_arg(args, unsigned long long), false, base, precision, width, flags);
#endif
}
else if (flags & FLAGS_LONG) {
print_integer(output, (printf_unsigned_value_t) va_arg(args, unsigned long), false, base, precision, width, flags);
}
else {
const unsigned int value =
(flags & FLAGS_CHAR) ? (unsigned char)va_arg(args, unsigned int) :
(flags & FLAGS_SHORT) ? (unsigned short int)va_arg(args, unsigned int) :
va_arg(args, unsigned int);
print_integer(output, (printf_unsigned_value_t) value, false, base, precision, width, flags);
}
}
break;
}
#if PRINTF_SUPPORT_DECIMAL_SPECIFIERS
case 'f' :
case 'F' :
if (*format == 'F') flags |= FLAGS_UPPERCASE;
print_floating_point(output, va_arg(args, double), precision, width, flags, PRINTF_PREFER_DECIMAL);
format++;
break;
#endif
#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
case 'e':
case 'E':
case 'g':
case 'G':
if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP;
if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE;
print_floating_point(output, va_arg(args, double), precision, width, flags, PRINTF_PREFER_EXPONENTIAL);
format++;
break;
#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
case 'c' : {
printf_size_t l = 1U;
// pre padding
if (!(flags & FLAGS_LEFT)) {
while (l++ < width) {
putchar_via_gadget(output, ' ');
}
}
// char output
putchar_via_gadget(output, (char) va_arg(args, int) );
// post padding
if (flags & FLAGS_LEFT) {
while (l++ < width) {
putchar_via_gadget(output, ' ');
}
}
format++;
break;
}
case 's' : {
const char* p = va_arg(args, char*);
if (p == NULL) {
out_rev_(output, ")llun(", 6, width, flags);
}
else {
printf_size_t l = strnlen_s_(p, precision ? precision : PRINTF_MAX_POSSIBLE_BUFFER_SIZE);
// pre padding
if (flags & FLAGS_PRECISION) {
l = (l < precision ? l : precision);
}
if (!(flags & FLAGS_LEFT)) {
while (l++ < width) {
putchar_via_gadget(output, ' ');
}
}
// string output
while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision)) {
putchar_via_gadget(output, *(p++));
--precision;
}
// post padding
if (flags & FLAGS_LEFT) {
while (l++ < width) {
putchar_via_gadget(output, ' ');
}
}
}
format++;
break;
}
case 'p' : {
width = sizeof(void*) * 2U + 2; // 2 hex chars per byte + the "0x" prefix
flags |= FLAGS_ZEROPAD | FLAGS_POINTER;
uintptr_t value = (uintptr_t)va_arg(args, void*);
(value == (uintptr_t) NULL) ?
out_rev_(output, ")lin(", 5, width, flags) :
print_integer(output, (printf_unsigned_value_t) value, false, BASE_HEX, precision, width, flags);
format++;
break;
}
case '%' :
putchar_via_gadget(output, '%');
format++;
break;
// Many people prefer to disable support for %n, as it lets the caller
// engineer a write to an arbitrary location, of a value the caller
// effectively controls - which could be a security concern in some cases.
#if PRINTF_SUPPORT_WRITEBACK_SPECIFIER
case 'n' : {
if (flags & FLAGS_CHAR) *(va_arg(args, char*)) = (char) output->pos;
else if (flags & FLAGS_SHORT) *(va_arg(args, short*)) = (short) output->pos;
else if (flags & FLAGS_LONG) *(va_arg(args, long*)) = (long) output->pos;
#if PRINTF_SUPPORT_LONG_LONG
else if (flags & FLAGS_LONG_LONG) *(va_arg(args, long long*)) = (long long int) output->pos;
#endif // PRINTF_SUPPORT_LONG_LONG
else *(va_arg(args, int*)) = (int) output->pos;
format++;
break;
}
#endif // PRINTF_SUPPORT_WRITEBACK_SPECIFIER
default :
putchar_via_gadget(output, *format);
format++;
break;
}
}
// termination
append_termination_with_gadget(output);
// return written chars without terminating \0
return (int)output->pos;
}
///////////////////////////////////////////////////////////////////////////////
int vprintf_(const char* format, va_list arg)
{
output_gadget_t gadget = extern_putchar_gadget();
return _vsnprintf(&gadget, format, arg);
}
int vsnprintf_(char* s, size_t n, const char* format, va_list arg)
{
output_gadget_t gadget = buffer_gadget(s, n);
return _vsnprintf(&gadget, format, arg);
}
int vsprintf_(char* s, const char* format, va_list arg)
{
return vsnprintf_(s, PRINTF_MAX_POSSIBLE_BUFFER_SIZE, format, arg);
}
int vfctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, va_list arg)
{
output_gadget_t gadget = function_gadget(out, extra_arg);
return _vsnprintf(&gadget, format, arg);
}
int printf_(const char* format, ...)
{
va_list args;
va_start(args, format);
const int ret = vprintf_(format, args);
va_end(args);
return ret;
}
int sprintf_(char* s, const char* format, ...)
{
va_list args;
va_start(args, format);
const int ret = vsprintf_(s, format, args);
va_end(args);
return ret;
}
int snprintf_(char* s, size_t n, const char* format, ...)
{
va_list args;
va_start(args, format);
const int ret = vsnprintf_(s, n, format, args);
va_end(args);
return ret;
}
int fctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, ...)
{
va_list args;
va_start(args, format);
const int ret = vfctprintf(out, extra_arg, format, args);
va_end(args);
return ret;
}
// Restore the previous diagnostic state
// Define PRINTF_DISABLE_WARNING_PRAGMAS to disable this block
#ifndef PRINTF_DISABLE_WARNING_PRAGMAS
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif // __GNUC__ || __clang__
#endif // PRINTF_DISABLE_WARNING_PRAGMAS
#ifdef __cplusplus
} // extern "C"
#endif