我正在研究速度至关重要且内存非常短的嵌入式 DSP。
目前,sprintf 在我的代码中使用了所有函数中最多的资源。我只用它来格式化一些简单的文本:%d, %e, %f, %s
,没有精确或奇异的操作。
如何实现更适合我使用的基本 sprintf 或 printf 函数?
这个假设存在itoa
将 int 转换为字符表示形式的 an,以及将fputs
字符串写出到任何你想要它去的地方。
浮点输出至少在一个方面是不合格的:它没有尝试按照标准要求正确舍入,所以如果你有(例如)一个1.234
在内部存储为的值1.2399999774
,它将被打印出来1.2399
而不是1.2340
. 这节省了相当多的工作,并且对于大多数典型目的来说仍然足够。
除了您询问的转换之外,这还支持%c
,但是如果您想摆脱它们,删除它们非常简单(这样做显然会节省一点内存)。%x
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <windows.h>
static void ftoa_fixed(char *buffer, double value);
static void ftoa_sci(char *buffer, double value);
int my_vfprintf(FILE *file, char const *fmt, va_list arg) {
int int_temp;
char char_temp;
char *string_temp;
double double_temp;
char ch;
int length = 0;
char buffer[512];
while ( ch = *fmt++) {
if ( '%' == ch ) {
switch (ch = *fmt++) {
/* %% - print out a single % */
case '%':
fputc('%', file);
length++;
break;
/* %c: print out a character */
case 'c':
char_temp = va_arg(arg, int);
fputc(char_temp, file);
length++;
break;
/* %s: print out a string */
case 's':
string_temp = va_arg(arg, char *);
fputs(string_temp, file);
length += strlen(string_temp);
break;
/* %d: print out an int */
case 'd':
int_temp = va_arg(arg, int);
itoa(int_temp, buffer, 10);
fputs(buffer, file);
length += strlen(buffer);
break;
/* %x: print out an int in hex */
case 'x':
int_temp = va_arg(arg, int);
itoa(int_temp, buffer, 16);
fputs(buffer, file);
length += strlen(buffer);
break;
case 'f':
double_temp = va_arg(arg, double);
ftoa_fixed(buffer, double_temp);
fputs(buffer, file);
length += strlen(buffer);
break;
case 'e':
double_temp = va_arg(arg, double);
ftoa_sci(buffer, double_temp);
fputs(buffer, file);
length += strlen(buffer);
break;
}
}
else {
putc(ch, file);
length++;
}
}
return length;
}
int normalize(double *val) {
int exponent = 0;
double value = *val;
while (value >= 1.0) {
value /= 10.0;
++exponent;
}
while (value < 0.1) {
value *= 10.0;
--exponent;
}
*val = value;
return exponent;
}
static void ftoa_fixed(char *buffer, double value) {
/* carry out a fixed conversion of a double value to a string, with a precision of 5 decimal digits.
* Values with absolute values less than 0.000001 are rounded to 0.0
* Note: this blindly assumes that the buffer will be large enough to hold the largest possible result.
* The largest value we expect is an IEEE 754 double precision real, with maximum magnitude of approximately
* e+308. The C standard requires an implementation to allow a single conversion to produce up to 512
* characters, so that's what we really expect as the buffer size.
*/
int exponent = 0;
int places = 0;
static const int width = 4;
if (value == 0.0) {
buffer[0] = '0';
buffer[1] = '\0';
return;
}
if (value < 0.0) {
*buffer++ = '-';
value = -value;
}
exponent = normalize(&value);
while (exponent > 0) {
int digit = value * 10;
*buffer++ = digit + '0';
value = value * 10 - digit;
++places;
--exponent;
}
if (places == 0)
*buffer++ = '0';
*buffer++ = '.';
while (exponent < 0 && places < width) {
*buffer++ = '0';
--exponent;
++places;
}
while (places < width) {
int digit = value * 10.0;
*buffer++ = digit + '0';
value = value * 10.0 - digit;
++places;
}
*buffer = '\0';
}
void ftoa_sci(char *buffer, double value) {
int exponent = 0;
int places = 0;
static const int width = 4;
if (value == 0.0) {
buffer[0] = '0';
buffer[1] = '\0';
return;
}
if (value < 0.0) {
*buffer++ = '-';
value = -value;
}
exponent = normalize(&value);
int digit = value * 10.0;
*buffer++ = digit + '0';
value = value * 10.0 - digit;
--exponent;
*buffer++ = '.';
for (int i = 0; i < width; i++) {
int digit = value * 10.0;
*buffer++ = digit + '0';
value = value * 10.0 - digit;
}
*buffer++ = 'e';
itoa(exponent, buffer, 10);
}
int my_printf(char const *fmt, ...) {
va_list arg;
int length;
va_start(arg, fmt);
length = my_vfprintf(stdout, fmt, arg);
va_end(arg);
return length;
}
int my_fprintf(FILE *file, char const *fmt, ...) {
va_list arg;
int length;
va_start(arg, fmt);
length = my_vfprintf(file, fmt, arg);
va_end(arg);
return length;
}
#ifdef TEST
int main() {
float floats[] = { 0.0, 1.234e-10, 1.234e+10, -1.234e-10, -1.234e-10 };
my_printf("%s, %d, %x\n", "Some string", 1, 0x1234);
for (int i = 0; i < sizeof(floats) / sizeof(floats[0]); i++)
my_printf("%f, %e\n", floats[i], floats[i]);
return 0;
}
#endif
我编写 nanoprintf 试图在微小的二进制大小和良好的特征覆盖之间找到平衡。截至今天,“基本”配置小于 1000 字节的二进制代码,“最大”配置(包括浮点解析)约为 2600 字节。100% C99 代码,无外部依赖,一个头文件。
https://github.com/charlesnicholson/nanoprintf
我还没有见过比这更小的 vsnprintf 实现,它具有类似的功能集。我还在公共领域发布了该软件,因此它完全不受阻碍。
这是一个使用 vsnprintf 功能的示例:
your_project_nanoprintf.c
#define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0
// Compile nanoprintf in this translation unit.
#define NANOPRINTF_IMPLEMENTATION
#include "nanoprintf.h"
your_log.h
void your_log(char const *s);
void your_log_v(char const *fmt, ...);
your_log.c
#include "your_log.h"
#include "nanoprintf.h"
#include <stdarg.h>
void your_log_v(char const *s) {
// Do whatever you want with the fully formatted string s.
}
void your_log(char const *fmt, ...) {
char buf[128];
va_arg args;
va_start(args, fmt);
npf_vsnprintf(buf, sizeof(buf), fmt, args); // Use nanoprintf for formatting.
va_end(args);
your_log_write(buf);
}
Nanoprintf 还提供了一个类似 snprintf 和一个自定义版本,该版本采用用户提供的 putc 回调来处理诸如 UART 写入之类的事情。
sprintf()
实现https://github.com/eyalroz/printf
sprintf()
您可能正在使用的标准库的实现可能会占用大量资源。但是您可以利用独立的sprintf()
实现,您可以获得更完整的功能,而无需支付如此多的内存使用。
现在,如果您告诉我们您只需要一些基本功能,您为什么要选择它?因为(s)printf()
使用的本质是我们在进行过程中倾向于使用它的更多方面。您注意到您想要打印更大的数字,或远十进制数字的差异;你想打印一堆值,然后决定你想让它们对齐。或者其他人想使用您添加的打印功能来打印您没有想到的东西。因此,您不必切换实现,而是使用一个实现,其中编译时选项配置哪些功能被编译,哪些功能被忽略。
我在这里添加了我自己的实现(v)sprintf
,但它不提供浮点支持(这就是我在这里的原因......)。
但是,它实现了说明符c
, s
, d
,u
和x
非标准说明符b
and m
(binary and memory hexdump); 还有标志0
, 1-9
, *
, +
.
#include <stdarg.h>
#include <stdint.h>
#define min(a,b) __extension__\
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a < _b ? _a : _b; })
enum flag_itoa {
FILL_ZERO = 1,
PUT_PLUS = 2,
PUT_MINUS = 4,
BASE_2 = 8,
BASE_10 = 16,
};
static char * sitoa(char * buf, unsigned int num, int width, enum flag_itoa flags)
{
unsigned int base;
if (flags & BASE_2)
base = 2;
else if (flags & BASE_10)
base = 10;
else
base = 16;
char tmp[32];
char *p = tmp;
do {
int rem = num % base;
*p++ = (rem <= 9) ? (rem + '0') : (rem + 'a' - 0xA);
} while ((num /= base));
width -= p - tmp;
char fill = (flags & FILL_ZERO)? '0' : ' ';
while (0 <= --width) {
*(buf++) = fill;
}
if (flags & PUT_MINUS)
*(buf++) = '-';
else if (flags & PUT_PLUS)
*(buf++) = '+';
do
*(buf++) = *(--p);
while (tmp < p);
return buf;
}
int my_vsprintf(char * buf, const char * fmt, va_list va)
{
char c;
const char *save = buf;
while ((c = *fmt++)) {
int width = 0;
enum flag_itoa flags = 0;
if (c != '%') {
*(buf++) = c;
continue;
}
redo_spec:
c = *fmt++;
switch (c) {
case '%':
*(buf++) = c;
break;
case 'c':;
*(buf++) = va_arg(va, int);
break;
case 'd':;
int num = va_arg(va, int);
if (num < 0) {
num = -num;
flags |= PUT_MINUS;
}
buf = sitoa(buf, num, width, flags | BASE_10);
break;
case 'u':
buf = sitoa(buf, va_arg(va, unsigned int), width, flags | BASE_10);
break;
case 'x':
buf = sitoa(buf, va_arg(va, unsigned int), width, flags);
break;
case 'b':
buf = sitoa(buf, va_arg(va, unsigned int), width, flags | BASE_2);
break;
case 's':;
const char *p = va_arg(va, const char *);
if (p) {
while (*p)
*(buf++) = *(p++);
}
break;
case 'm':;
const uint8_t *m = va_arg(va, const uint8_t *);
width = min(width, 64); // buffer limited to 256!
if (m)
for (;;) {
buf = sitoa(buf, *(m++), 2, FILL_ZERO);
if (--width <= 0)
break;
*(buf++) = ':';
}
break;
case '0':
if (!width)
flags |= FILL_ZERO;
// fall through
case '1'...'9':
width = width * 10 + c - '0';
goto redo_spec;
case '*':
width = va_arg(va, unsigned int);
goto redo_spec;
case '+':
flags |= PUT_PLUS;
goto redo_spec;
case '\0':
default:
*(buf++) = '?';
}
width = 0;
}
*buf = '\0';
return buf - save;
}
int my_sprintf(char * buf, const char * fmt, ...)
{
va_list va;
va_start(va,fmt);
int ret = my_vsprintf(buf, fmt, va);
va_end(va);
return ret;
}
#if TEST
int main(int argc, char *argv[])
{
char b[256], *p = b;
my_sprintf(b, "%x %d %b\n", 123, 123, 123);
while (*p)
putchar(*p++);
}
#endif