I am trying to dereferencing a pointer, according to the data-type passed to the function, just to learn.
code 1: First Try. Oviously it didn't work.
typedef enum
{
bits_8,
bits_16,
bits_32
}bits_width;
inline int32_t write_data_spi1_enhanced_buffer_x_bits( void* data_address, uint32_t size, bits_width data_type )
{
switch( data_type )
{
case bits_8:
spi1_change_mode_8_bits();
uint8_t* dt = (uint8_t*)data_address;
break;
case bits_16:
spi1_change_mode_16_bits();
uint16_t* dt = (uint16_t*)data_address;
break;
case bits_32:
spi1_change_mode_32_bits();
uint32_t* dt = (uint32_t*)data_address;
break;
default:
return -1;
break;
}
for( uint32_t i = 0 ; i < size ; i++ )
{
while( SPI1STATbits.SPITBF );
SPI1BUF = *dt++; // Write the data out to the SPI1 peripheral. *dt++ = dt[i].
}
return 0;
}
code 2: Looks OK(compile). Not tested in hardware.
typedef enum
{
bits_8,
bits_16,
bits_32
}bits_width;
inline int32_t write_data_spi1_enhanced_buffer_x_bits( uint8_t* data_address, uint32_t size, bits_width data_type )
{
uint32_t x;
switch( data_type )
{
case bits_8:
spi1_change_mode_8_bits();
x = 1;
break;
case bits_16:
spi1_change_mode_16_bits();
x = 2;
break;
case bits_32:
//spi1_change_mode_32_bits();
x = 4;
break;
default:
return -1;
break;
}
for( uint32_t i = 0 ; i < size ; i++ )
{
while( SPI1STATbits.SPITBF );
SPI1BUF = *( data_address + (i*x) ); // Write the data out to the SPI1 peripheral.
}
return 0;
}
code 3: I did not want to write the for loop 3 times(one for each data type).
typedef enum
{
bits_8,
bits_16,
bits_32
}bits_width;
inline int32_t write_data_spi1_enhanced_buffer_x_bits( void* data_address, uint32_t size, bits_width data_type )
{
switch( data_type )
{
case bits_8:
spi1_change_mode_8_bits();
uint8_t* dt = (uint8_t*)data_address;
for( uint32_t i = 0 ; i < size ; i++ )
{
while( SPI1STATbits.SPITBF );
SPI1BUF = *dt++; // Write the data out to the SPI1 peripheral. *dt++ = dt[i].
}
break;
case bits_16:
spi1_change_mode_16_bits();
uint16_t* dt = (uint16_t*)data_address;
for( uint32_t i = 0 ; i < size ; i++ )
{
while( SPI1STATbits.SPITBF );
SPI1BUF = *dt++; // Write the data out to the SPI1 peripheral. *dt++ = dt[i].
}
break;
case bits_32:
spi1_change_mode_32_bits();
uint32_t* dt = (uint32_t*)data_address;
for( uint32_t i = 0 ; i < size ; i++ )
{
while( SPI1STATbits.SPITBF );
SPI1BUF = *dt++; // Write the data out to the SPI1 peripheral. *dt++ = dt[i].
}
break;
default:
return -1;
break;
}
return 0;
}
How to make code 1 work ?
Or is there a better solution ?
Thank`s.
You could create 3 separate functions using a macro:
#define TFUNC(X) \
inline void funcdef_##X(X* dt, uint32_t size) { \
spi1_change_mode_##X(); \
for (uint32_t i = 0; i < size; i++) { \
while (SPI1STATbits.SPITBF) \
; \
SPI1BUF = *dt++; \
} \
}
TFUNC(uint8_t)
TFUNC(uint16_t)
TFUNC(uint32_t)
Note: You'd have to rename spi1_change_mode_8_bits into spi1_change_mode_uint8_t etc. for the above to work.
Then use a _Generic as a front-end:
#define write_data_spi1_enhanced_buffer_x_bits(X,S) \
_Generic((X), \
uint8_t* : funcdef_uint8_t, \
uint16_t* : funcdef_uint16_t, \
uint32_t* : funcdef_uint32_t) \
(X, S)
You'd then call it by a cast of data_address to the proper type and it'll select the correct function to call. Example:
inline int32_t rite_data_spi1_enhanced_buffer(void* data_address, uint32_t size,
bits_width data_type) {
switch (data_type) {
case bits_8:
write_data_spi1_enhanced_buffer_x_bits((uint8_t*)data_address, size);
break;
case bits_16:
write_data_spi1_enhanced_buffer_x_bits((uint16_t*)data_address, size);
break;
case bits_32:
write_data_spi1_enhanced_buffer_x_bits((uint32_t*)data_address, size);
break;
default:
return 1;
}
return 0;
}
I do not like this C "generic" syntax.
I would simple:
Avoid pointer punning
Set the register in the switch() ... case
inline int32_t write_data_spi1_enhanced_buffer_x_bits( void* data_address, uint32_t size, bits_width data_type )
{
unsigned char *uda = data_address;
uint16_t u16;
uint32_t u32;
for( uint32_t i = 0 ; i < size ; i++ )
{
while( SPI1STATbits.SPITBF );
switch( data_type )
{
case bits_8:
SPI1BUF = uda[i];
break;
case bits_16:
memcpy(&u16, uda + sizeof(u16) * i, sizeof(u16));
SPI1BUF = u16;
break;
case bits_32:
memcpy(&u32, uda + sizeof(u32) * i, sizeof(u32));
SPI1BUF = u32;
break;
default:
return -1;
break;
}
}
}
memcpy will be optimized out on targets not needing special alignment.
Related
i have an uint8_t Variable which contains a substring of 4 hexadecimal variables. Example:
uint8_t String[10] = "00AABBCC";
I would like to take these 4 hex Variables into different hex values:
uint8_t Data_Byte[4];
Data_Byte[0]=0x00;
Data_Byte[1]=0xAA;
Data_Byte[2]=0xBB;
Data_Byte[3]=0xCC;
How can I take these 4 substrings into 4 different uint8_t Variables?
You can use sscanf to parse each two-character pair in the string into a number:
uint8_t arr[strlen(String) / 2];
for (int i = 0; i < strlen(String); i += 2) {
sscanf(String + i, "%2hhx", &arr[i / 2]);
}
If you're developing on a system with limited sscanf support, you can use something like this:
for (int i = 0; i < strlen(String); i += 2) {
uint8_t val1 = isdigit(String[i]) ? (String[i] - '0') : (String[i] - 'A' + 10);
uint8_t val2 = isdigit(String[i + 1]) ? (String[i + 1] - '0') : (String[i + 1] - 'A' + 10);
arr[i / 2] = val1 << 4 | val2;
}
With your stipulation the strings will represent 4 bytes, this a far-easier-to-read-and-understand solution IMO. I have no comment on efficiency.
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <arpa/inet.h>
bool convert(const uint8_t* strValue, uint8_t* cvrtArray)
{
// make 2nd parameter non-NULL for better error checking
errno = 0;
char* endptr = NULL;
// convert to unsigned long
unsigned long val = strtoul((const char*)strValue, &endptr, 16);
// do some error checking, this probably needs some improvements
if (errno == ERANGE && val == ULONG_MAX)
{
fprintf(stderr, "Overflow\n");
return false;
}
else if ((strValue != NULL) && (*endptr != '\0'))
{
fprintf(stderr, "Cannot convert\n");
return false;
}
// potential need to flip the bytes (your string is big endian, and the
// test machine on godbolt is little endian)
val = htonl(val);
// copy to our array
memcpy(cvrtArray, &val, 4);
return true;
}
int main(void)
{
uint8_t Data_Byte[4] = { 0 };
uint8_t String[10] = "00AABBCC";
if (convert(String, Data_Byte) == true)
{
for(size_t i=0; i<sizeof Data_Byte; i++)
{
printf("Data_Byte[%zu] = 0x%02" PRIX8 "\n", i, Data_Byte[i]);
}
}
else
{
fprintf(stderr, "There was a problem converting %s to byte array\n", String);
}
return 0;
}
code in action
I took some inspiration from 0___________ and made my own:
static char digits[] = "0123456789ABCDEF";
void convert(uint8_t *chrs, uint8_t *buff)
{
size_t len = strlen((char *)chrs);
size_t i;
for(i = 0; i < len; i+=2) {
buff[i / 2] = (strchr(digits, chrs[i]) - digits);
buff[i / 2] += (strchr(digits, chrs[i+1]) - digits) << 4;
}
if(i<len)
buff[i / 2] = (strchr(digits, chrs[i]) - digits);
}
The changes are that I find it much more natural to do a complete element in every iteration. To account for odd length input strings, I just added an if statement in the end that takes care of it. This can be removed if input strings always have even length. And I skipped returning the buffer for simplicity. However, as 0___________ pointed out in comments, there are good reasons to return a pointer to the output buffer. Read about those reasons here: c++ memcpy return value
static char digits[] = "0123456789ABCDEF";
uint8_t *convert(uint8_t *chrs, uint8_t *buff)
{
size_t len = strlen((char *)chrs);
for(size_t i = 0; i < len; i++)
{
int is_first_digit = !(i & 1);
int shift = is_first_digit << 2;
buff[i / 2] += (strchr(digits, chrs[i]) - digits) << shift;
}
return buff;
}
int main(void)
{
uint8_t String[] = "00AABBCC";
uint8_t buff[4];
convert(String, buff);
for(size_t i = 0; i < sizeof(buff); i++)
{
printf("%hhx", buff[i]); // I know it is wrong format
}
}
https://godbolt.org/z/9c8aexTvq
Or even faster solution:
int getDigit(uint8_t ch)
{
switch(ch)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return ch - '0';
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
return ch - 'A' + 10;
}
return 0;
}
uint8_t *convert(uint8_t *chrs, uint8_t *buff)
{
size_t len = strlen((char *)chrs);
for(size_t i = 0; i < len; i++)
{
int is_first_digit = !(i & 1);
int shift = is_first_digit << 2;
buff[i / 2] += (getDigit(chrs[i])) << shift;
}
return buff;
}
Remember: use functions for this kind of tasks. Do not program in main.
im just came into the question with dynamic type of variable (not really dynamic, but should be determind in the runtime), the situation is like this:
i have a function which accept a double array convert it into the integer and write to a file, the integer can have different Bitlength, like 8, 16 and 32. As it is a array, i want to use a pointer to access the final result (array). So i use the void pointer with malloc and switch case now, but it will be needed to add switch case every where when i trying to access or modify this array, my question is, is there a better way to do this?
current code is like:
void foo(double * arr, int len, int iBits, FILE *fh)
{
void * newArr;
int iBytePerElement, iBase,i;
iBytePerElement = iBits / 8;
iBase = (1 << (iBits - 1)) - 1;
switch (iBytePerElement)
{
case 1:
{
newArr = (int8_t *) malloc(sizeof(int8_t)*len);
break;
}
case 2:
{
newArr = (int16_t *) malloc(sizeof(int16_t)*len);
break;
}
case 4:
{
newArr = (int32_t *) malloc(sizeof(int32_t)*len);
break;
}
}
for (i = 0; i < len; ++i)
{
switch (iBitPerElement)
{
case 1:
{
((int8_t *)newArr)[i] = (int8_t)(arr[i]*iBase);
break;
}
case 2:
{
((int16_t *)newArr)[i] = (int16_t)(arr[i]*iBase);
break;
}
case 4:
{
((int32_t *)newArr)[i] = (int32_t)(arr[i]*iBase);
break;
}
}
}
fwrite(newArr, iBytePerElement, iBytePerElement*len,fh);
}
Making a few assumptions about what you probably meant where your example code is undefined (commented), here's how I might do it:
#include <stdint.h>
#include <stdlib.h>
#include <limits.h>
void *foo(double * arr, int len, int iBits) //should return the malloced array not void
{
void * newArr;
int iBytePerElement, iBase, i;
iBytePerElement = iBits / 8;
iBase = (1 << (iBits - 1)) - 1;
if(NULL==(newArr = malloc(iBits/CHAR_BIT))) return NULL; //replace the first switch
//which obviously meant to alloc sizeof(int16_t)*len in the case 2 branch (not (sizeof(int8_t)*len) etc
for (i = 0; i < len; ++i) {
switch(iBytePerElement){
break; case 1: ((int8_t *)newArr)[i] = arr[i]*iBase;
break; case 2: ((int16_t *)newArr)[i] = arr[i]*iBase;
break; case 4: ((int32_t *)newArr)[i] = arr[i]*iBase;
}
}
return newArr;
}
Basically, you only need the second switch.
If you wanted to get rid of the 2nd switch, you could replace it with some function pointer play.
For example:
#include <stdint.h>
#include <stdlib.h>
#include <limits.h>
#define MK_FN(Bits) \
void to##Bits(void *newArr, double const*arr, int len) \
{ \
int i; for(i=0; i < len; i++) ((int##Bits##_t *)newArr)[i] = arr[i]*((1<<(Bits-1)-1)); \
}
MK_FN(8)
MK_FN(16)
MK_FN(32)
void *foo(double * arr, int len, int iBits) //should return the malloced array not void
{
void * newArr;
int iBytePerElement = iBits / 8;
if(NULL==(newArr = malloc(iBits/CHAR_BIT))) return NULL;
((void (*[])(void *,double const*, int)){ [1]=to8, [2]=to16, [4]=to32, })[iBits](newArr,arr,len);
return newArr;
}
I have a basic question regarding a c problem I'm having. My input char array would be something like:
'DABC95C1'
and I want to make an uint8_t array out of it
0xDA 0xBC 0x95 0xC1
I have easily access to each char but I dont know how I can form 0xDA. Is there function in c or can i just cast it?
Use the strtoull function to convert a string to a number in a given base. Then just shift out the desired bytes. Such as:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
unsigned long long res = strtoull("DABC95C1", NULL, 16);
printf("%hhx, %hhx, %hhx, %hhx",
(unsigned char)res,
(unsigned char)((res >> 8) & 0xFF),
(unsigned char)((res >> 16) & 0xFF),
(unsigned char)((res >> 24) & 0xFF)
);
return 0;
}
result:
c1, 95, bc, da
Demo
Notes:
As your requirement is to get an array of bytes, you might be tempted to do something like
uint8_t *arr = (uint8_t*)&res;
But here are two caveats in this:
1) I is a strict aliasing rule violation (you can somehow to work around it by replacing uint8_t with char)
2) The order of the returned bytes will be implementation specific (endianness dependent) and thus not portable. Also note that the result is unsigned long long, so you might get extra padding zeros as either the beginning of the array or in the end of it.
Any size string in the chosen order. Portable digit conversion and it optimizes very well on the ASCII systems. https://godbolt.org/g/Ycah1e
#include <stdio.h>
#include <stdint.h>
#include <string.h>
int CharToDigit(const char c);
void *StringToTable(const char *str, const void *buff, const int order)
{
uint8_t *ptr = (uint8_t *)buff;
size_t len;
int incr = order ? 1 : -1;
if(buff && str)
{
len = strlen(str);
if(len &1) return NULL;
ptr += order ? 0 : len / 2 - 1;
while(*str)
{
int d1 = CharToDigit(*str++);
int d2 = CharToDigit(*str++);
if(d1 == -1 || d2 == -1) return NULL;
*ptr = d1 * 16 + d2;
ptr += incr;
}
}
return buff;
}
int main(void) {
int index = 0;
char *str = "78deAc8912fF0f3B";
uint8_t buff[strlen(str) / 2];
StringToTable(str, buff, 0);
printf("String: %s\nResult: ", str);
for(index = 0; index < strlen(str) / 2; index++ )
{
printf("[0x%02hhx]", buff[index] );
}
printf("\n");
StringToTable(str, buff, 1);
printf("String: %s\nResult: ", str);
for(index = 0; index < strlen(str) / 2; index++ )
{
printf("[0x%02hhx]", buff[index] );
}
printf("\n");
return 0;
}
int CharToDigit(const char c)
{
switch(c)
{
case 'a':
case 'A':
return 10;
case 'b':
case 'B':
return 11;
case 'c':
case 'C':
return 12;
case 'd':
case 'D':
return 13;
case 'e':
case 'E':
return 14;
case 'f':
case 'F':
return 15;
case '0':
return 0;
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
case '8':
return 8;
case '9':
return 9;
default:
return -1;
}
}
You can conver a character to an int like so
static inline int char2int(char Ch)
{
return(Ch>='0'&&Ch<='9')?(Ch-'0'):(Ch-'A'+10);
//assuming correct input with no lowercase letters
}
Two characters then with
static inline
int chars2int(unsigned char const Chars[2])
{
return (char2int(Chars[0])<<4)|(char2int(Chars[1]));
}
And several characters by converting each pair:
static inline int char2int(char Ch)
{
return(Ch>='0'&&Ch<='9')?(Ch-'0'):(Ch-'A'+10);
}
static inline
int chars2int(unsigned char const Chars[2])
{
return (char2int(Chars[0])<<4)|(char2int(Chars[1]));
}
#include <stdio.h>
#include <string.h>
#include <assert.h>
int main()
{
char const inp[] = "DABC95C1";
assert((sizeof(inp)-1)%2==0);
unsigned i;
unsigned char out[(sizeof(inp)-1)/2];
for(i=0;i<sizeof(inp);i+=2){
out[i/2]=chars2int((unsigned char*)inp+i);
}
for(i=0;i<sizeof(out);i++)
printf("%2x\n", out[i]);
}
I'm working on an embedded DSP where speed is crucial, and memory is very short.
At the moment, sprintf uses the most resources of any function in my code. I only use it to format some simple text: %d, %e, %f, %s, nothing with precision or exotic manipulations.
How can I implement a basic sprintf or printf function that would be more suitable for my usage?
This one assumes the existence of an itoa to convert an int to character representation, and an fputs to write out a string to wherever you want it to go.
The floating point output is non-conforming in at least one respect: it makes no attempt at rounding correctly, as the standard requires, so if you have have (for example) a value of 1.234 that is internally stored as 1.2399999774, it'll be printed out as 1.2399 instead of 1.2340. This saves quite a bit of work, and remains sufficient for most typical purposes.
This also supports %c and %x in addition to the conversions you asked about, but they're pretty trivial to remove if you want to get rid of them (and doing so will obviously save a little memory).
#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
I wrote nanoprintf in an attempt to find a balance between tiny binary size and having good feature coverage. As of today the "bare-bones" configuration is < 800 bytes of binary code, and the "maximal" configuration including float parsing is < 2500 bytes. 100% C99 code, no external dependencies, one header file.
https://github.com/charlesnicholson/nanoprintf
I haven't seen a smaller vsnprintf implementation than this that has a comparable feature set. I also released the software in the public domain and with the Zero-clause BSD license so it's fully unencumbered.
Here's an example that uses the vsnprintf functionality:
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 also provides an snprintf-alike and a custom version that takes a user-provided putc callback for things like UART writes.
I add here my own implementation of (v)sprintf, but it does not provide float support (it is why I am here...).
However, it implements the specifiers c, s, d, u, x and the non standard ones b and m (binary and memory hexdump); and also the flags 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
tl;dr : Considering a smaller, but more complete, sprintf() implementation
https://github.com/eyalroz/printf
The standard library's sprintf() implementation you may be using is probably quite resource-taxing. But it's possible that you could avail yourself of a stand-alone sprintf() implementation, you would get more complete functionality without paying with so much memory use.
Now, why would you choose that if you've told us you only need some basic functionality? Because the nature of (s)printf() use is that we tend to use more aspects of it as we go along. You notice you want to print larger numbers, or differences in far decimal digits; you want to print a bunch of values and then decide you want them aligned. Or somebody else wants to use the printing capability you added to print something you haven't thought of. So, instead of having to switch implementations, you use an implementation where compile-time options configure which features get compiled and which get left out.
The code below is extracted from nginx,which basically rewrites sprintf,in fact nginx also rewrites some other string functions,is it worth the effort?
u_char *
ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args)
{
u_char *p, zero;
int d;
double f, scale;
size_t len, slen;
int64_t i64;
uint64_t ui64;
ngx_msec_t ms;
ngx_uint_t width, sign, hex, max_width, frac_width, n;
ngx_str_t *v;
ngx_variable_value_t *vv;
while (*fmt && buf < last) {
/*
* "buf < last" means that we could copy at least one character:
* the plain character, "%%", "%c", and minus without the checking
*/
if (*fmt == '%') {
i64 = 0;
ui64 = 0;
zero = (u_char) ((*++fmt == '0') ? '0' : ' ');
width = 0;
sign = 1;
hex = 0;
max_width = 0;
frac_width = 0;
slen = (size_t) -1;
while (*fmt >= '0' && *fmt <= '9') {
width = width * 10 + *fmt++ - '0';
}
for ( ;; ) {
switch (*fmt) {
case 'u':
sign = 0;
fmt++;
continue;
case 'm':
max_width = 1;
fmt++;
continue;
case 'X':
hex = 2;
sign = 0;
fmt++;
continue;
case 'x':
hex = 1;
sign = 0;
fmt++;
continue;
case '.':
fmt++;
while (*fmt >= '0' && *fmt <= '9') {
frac_width = frac_width * 10 + *fmt++ - '0';
}
break;
case '*':
slen = va_arg(args, size_t);
fmt++;
continue;
default:
break;
}
break;
}
switch (*fmt) {
case 'V':
v = va_arg(args, ngx_str_t *);
len = ngx_min(((size_t) (last - buf)), v->len);
buf = ngx_cpymem(buf, v->data, len);
fmt++;
continue;
case 'v':
vv = va_arg(args, ngx_variable_value_t *);
len = ngx_min(((size_t) (last - buf)), vv->len);
buf = ngx_cpymem(buf, vv->data, len);
fmt++;
continue;
case 's':
p = va_arg(args, u_char *);
if (slen == (size_t) -1) {
while (*p && buf < last) {
*buf++ = *p++;
}
} else {
len = ngx_min(((size_t) (last - buf)), slen);
buf = ngx_cpymem(buf, p, len);
}
fmt++;
continue;
case 'O':
i64 = (int64_t) va_arg(args, off_t);
sign = 1;
break;
case 'P':
i64 = (int64_t) va_arg(args, ngx_pid_t);
sign = 1;
break;
case 'T':
i64 = (int64_t) va_arg(args, time_t);
sign = 1;
break;
case 'M':
ms = (ngx_msec_t) va_arg(args, ngx_msec_t);
if ((ngx_msec_int_t) ms == -1) {
sign = 1;
i64 = -1;
} else {
sign = 0;
ui64 = (uint64_t) ms;
}
break;
case 'z':
if (sign) {
i64 = (int64_t) va_arg(args, ssize_t);
} else {
ui64 = (uint64_t) va_arg(args, size_t);
}
break;
case 'i':
if (sign) {
i64 = (int64_t) va_arg(args, ngx_int_t);
} else {
ui64 = (uint64_t) va_arg(args, ngx_uint_t);
}
if (max_width) {
width = NGX_INT_T_LEN;
}
break;
case 'd':
if (sign) {
i64 = (int64_t) va_arg(args, int);
} else {
ui64 = (uint64_t) va_arg(args, u_int);
}
break;
case 'l':
if (sign) {
i64 = (int64_t) va_arg(args, long);
} else {
ui64 = (uint64_t) va_arg(args, u_long);
}
break;
case 'D':
if (sign) {
i64 = (int64_t) va_arg(args, int32_t);
} else {
ui64 = (uint64_t) va_arg(args, uint32_t);
}
break;
case 'L':
if (sign) {
i64 = va_arg(args, int64_t);
} else {
ui64 = va_arg(args, uint64_t);
}
break;
case 'A':
if (sign) {
i64 = (int64_t) va_arg(args, ngx_atomic_int_t);
} else {
ui64 = (uint64_t) va_arg(args, ngx_atomic_uint_t);
}
if (max_width) {
width = NGX_ATOMIC_T_LEN;
}
break;
case 'f':
f = va_arg(args, double);
if (f < 0) {
*buf++ = '-';
f = -f;
}
ui64 = (int64_t) f;
buf = ngx_sprintf_num(buf, last, ui64, zero, 0, width);
if (frac_width) {
if (buf < last) {
*buf++ = '.';
}
scale = 1.0;
for (n = frac_width; n; n--) {
scale *= 10.0;
}
/*
* (int64_t) cast is required for msvc6:
* it can not convert uint64_t to double
*/
ui64 = (uint64_t) ((f - (int64_t) ui64) * scale + 0.5);
buf = ngx_sprintf_num(buf, last, ui64, '0', 0, frac_width);
}
fmt++;
continue;
#if !(NGX_WIN32)
case 'r':
i64 = (int64_t) va_arg(args, rlim_t);
sign = 1;
break;
#endif
case 'p':
ui64 = (uintptr_t) va_arg(args, void *);
hex = 2;
sign = 0;
zero = '0';
width = NGX_PTR_SIZE * 2;
break;
case 'c':
d = va_arg(args, int);
*buf++ = (u_char) (d & 0xff);
fmt++;
continue;
case 'Z':
*buf++ = '\0';
fmt++;
continue;
case 'N':
#if (NGX_WIN32)
*buf++ = CR;
#endif
*buf++ = LF;
fmt++;
continue;
case '%':
*buf++ = '%';
fmt++;
continue;
default:
*buf++ = *fmt++;
continue;
}
if (sign) {
if (i64 < 0) {
*buf++ = '-';
ui64 = (uint64_t) -i64;
} else {
ui64 = (uint64_t) i64;
}
}
buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width);
fmt++;
} else {
*buf++ = *fmt++;
}
}
return buf;
}
IMO writing similar stuff only causes waste of memory,your idea?
Whether it's "worth it" is subjective - there are lots of things to consider:
What advantages does it give over sprintf? Do you need them?
Are you willing to live with whatever shortcomings may be there?
Do you understand the code well enough to be able to fix it if problems are found?
Is it as fast as sprintf? If not, do you care?
You may also want to consider writing a cover function that adds whatever extra functionality you need but then just calls sprintf, rather than re-implement the entire function.
I think this kind of thing is not only wasteful but actively harmful. The name includes printf, which would lead a reasonable person first seeing code that's using it to assume its format strings are printf-compatible. But in fact they're only a very poor approximation of printf semantics. This could lead to extremely serious, security-critical bugs, which might go undetected if the code only appears in a non-common-usage case. The C standard has a perfectly safe and usable snprintf function which should always be used when this functionality is needed. And shame on whoever designed this junk in the name of "security"...