Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

printf by given pointer and format string. Issue with floats

As I want to keep track of some variables in order to see how they change, I want to create a function, which receives a format string, depending on the variable's type and a pointer to the value. I hope, that with the help of the format string, printf will properly determine the value. Actually it works, but with one exception - the float values do not print properly.

This is my code:

#include <stdio.h>

void pprint (char* fmtstr, void* p)
{
        printf(fmtstr,*(long double *)p);
}

int main (int argc, char **argv)
{
        char cval = 64;
        int ival = -534;
        unsigned int iuval = 535;
        float fval = 534.64;
        double dval = 53432.1;
        long double ldval = 534321234.134567;
        long long lval = -654321;
        unsigned long long luval = 7654321;

        pprint ("char: %hhd\n",&cval);
        pprint ("int: %d\n",&ival);
        pprint ("uint: %u\n",&iuval);
        pprint ("float: %f\n",&fval);
        pprint ("double: %f\n",&dval);
        pprint ("long double: %Lf\n",&ldval);
        pprint ("llong: %lld\n",&lval);
        pprint ("ullong: %llu\n",&luval);
        return 0;
}

And the results are:

char: 64
int: -534
uint: 535
float: 0.000000
double: 53432.100000
long double: 534321234.134567
llong: -654321
ullong: 7654321

As we can see, everything is printed OK, except the float. However, after some modification of the pprint function (casting the void pointer to float):

printf(fmtstr,*(float*)p);

The results are:

char: 0
int: 1073741824
uint: 0
float: 534.640015
double: 0.000000
long double: 0.000000
llong: -351285912010752
ullong: 4038940431088615424

Now only the float value is printed properly. Another side effect is, that casting to any other type results in successful printf of types with lower or same size. (If I cast to int, it will print properly chars, but not longs). So, casting to long double solves this problem, as it hs the largest size.

However, the problem with float remains. In order to print the float value, I need to cast the pointer to float, but nothing else. And the opposite: when I cast to float, everything except float fails. Isn't the dereference related to reading the data, located on the pointed address and passing it to printf? (the same pointer, casted to any type holds the same address?) Then, the format string is able to "read" the given bytes in the proper format - char, int, unsigned int, etc.

I have heard, that float values in variadic functions are converted to doubles. Is that relaated to the problem? Also, in some cases I'm unable to provide the value as double - because for example many of the variables, used in Opengl are floats (GLfloat).

In summary, I have 2 questions.

  1. Why floats behave different from all another types.

  2. Can the use of this function lead to side effects? I mean, that when printing for example an int, printf receives 12 bytes (sizeof(long double)) as second parameter, but reads only 4 ("%d" format string). From these 12 bytes the first 4 belong to the int value, that I want to print, and the next 8 are junk, which are never read by printf. Is this a problem?

Thank you.

like image 763
Nuclear Avatar asked Sep 08 '11 18:09

Nuclear


4 Answers

This answers neither of your questions, but you might want to look into vprintf if you're trying to wrap printf.

like image 119
nmichaels Avatar answered Oct 16 '22 13:10

nmichaels


You're lucky (or unlucky) it printed what you expected for so many of those types.

The C standard says, in §7.19.6.1/9

If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.

In particular, many implementations use different CPU registers to pass floats and integers to printf(), and even though an integer is written in the source code, "%f" makes printf() access a floating-point register, or vice versa.

As an example, on my system, the output of your programs is

char: -1
int: 164124920
uint: 164124916
float: 0.000000
double: 0.000000
long double: 534321234.134567
llong: 140733357512904
ullong: 140733357512896

and

char: -17
int: 1200847080
uint: 1200847076
float: 534.640015
double: 0.000000
long double: 0.000000
llong: 140734394235064
ullong: 140734394235056
like image 32
Cubbi Avatar answered Oct 16 '22 12:10

Cubbi


The approach you are trying to use is not even remotely viable. It cannot be done this way. If you insist on the "a format string and a pointer" interface for your printf wrapper function, then the only way to make it work is to manually parse the format string, determine the actual type of the argument and cast (and dereference) the pointer value with the proper type. There's no"one cast fits all" solution in this case.

Your questions has no definitive answer in general case. Your program has undefined behavior for more reasons than one. Specific practical behavior will depend on the implementation and, possibly, on other factors.

Again, the only way to make it work is to manually parse the format string, which is not a good idea either. If you could explain where the "a format string and a pointer" interface came from, maybe we could suggest a more reasonable interface.

like image 4
AnT Avatar answered Oct 16 '22 13:10

AnT


  1. The floats behave differently because the format specificer is expecting a 32bit sized type and you're sending it a 64 bit sized type. For the other types, you got lucky with endianness.

  2. Listing the side affects of using a library function incorrectly is moot.

Fix the code. I'd look at the vprintf() and vsprintf() functions, or better yet, state your design purposes, there might be a simpler, more viable solution than the code you've shown.

like image 2
Jamie Avatar answered Oct 16 '22 11:10

Jamie