printf parameter type wrangling

M

Michael B Allen

I'm printing tables of structure members in a terminal and I would like
to use printf with an arbitrary pointer so that the code is smaller and
I don't need to switch over ever possible type.

But the question is - can I pass an arbitrary type (short, double, char *,
etc) to printf cast as a char * (provided the specified format string is
appropriate of course)?

I chose 'char *' because I reasoned it (a pointer) would large enough
to accommodate any primitive type passed as a function parameter.

#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>

void
printf_member(void *obj, size_t off, const char *fmt)
{
char **p;

p = (char **)((char *)obj + off);

printf(fmt, *p);
}

struct foo {
char c;
float f;
int i;
char *s;
};

int
main(void)
{
struct foo f = { 'c', 2.34f, 100, "hello, world" };

printf_member(&f, offsetof(struct foo, c), "[c]\n");
printf_member(&f, offsetof(struct foo, f), "[%f]\n");
printf_member(&f, offsetof(struct foo, i), "[%d]\n");
printf_member(&f, offsetof(struct foo, s), "[%s]\n");

return 0;
}

Unfortunately this technique does not appear to work as the float member
was not printed properly. Why?

$ gcc -Wall -W -ansi -pedantic -o pa pa.c
$ ./pa
[c]
[0.000000]
[100]
[hello, world]

Thanks,
Mike
 
E

Emmanuel Delahaye

Michael B Allen wrote on 21/08/05 :
I'm printing tables of structure members in a terminal and I would like
to use printf with an arbitrary pointer so that the code is smaller and
$ gcc -Wall -W -ansi -pedantic -o pa pa.c
$ ./pa
[c]
[0.000000]
[100]
[hello, world]

Sounds too complicated to me... This is how I do :

#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>

#define FMT_S "%-5s"

#define PRT_C(struct_, field_) \
printf (FMT_S" = '%c'\n" \
, #struct_ "." #field_ \
, struct_.field_)

#define PRT_F(struct_, field_) \
printf (FMT_S" = %f\n" \
, #struct_ "." #field_ \
, struct_.field_)

#define PRT_I(struct_, field_) \
printf (FMT_S" = %d\n" \
, #struct_ "." #field_ \
, struct_.field_)

#define PRT_S(struct_, field_) \
printf (FMT_S" = '%s'\n" \
, #struct_ "." #field_ \
, struct_.field_)

struct foo
{
char c;
float f;
int i;
char *s;
};

int main (void)
{
struct foo f =
{'c', 2.34f, 100, "hello, world"};

PRT_C (f, c);
PRT_F (f, f);
PRT_I (f, i);
PRT_S (f, s);

return 0;
}

Output :

f.c = 'c'
f.f = 2.340000
f.i = 100
f.s = 'hello, world'

--
Emmanuel
The C-FAQ: http://www.eskimo.com/~scs/C-faq/faq.html
The C-library: http://www.dinkumware.com/refxc.html

..sig under repair
 
R

Richard Tobin

Michael B Allen said:
But the question is - can I pass an arbitrary type (short, double, char *,
etc) to printf cast as a char * (provided the specified format string is
appropriate of course)?
No.

I chose 'char *' because I reasoned it (a pointer) would large enough
to accommodate any primitive type passed as a function parameter.

There are two problems here.

First, you are accessing a char * at a place in the struct that does
not necessarily have one. If the object at that location is smaller
than a char * (a char or short perhaps), then you will get some extra
rubbish from another part of the struct or perhaps some (arbitrary)
padding. If it is larger (a double perhaps), you won't get all of it.

Even if the thing there is the same size as a char *, you might (on
some architectures) get some bit pattern that causes an error when
treated as a char * (e.g. when loaded into a address register).
This doesn't happen on most architectures, but might in theory.

Second, you are passing a char * to printf when it expects something
else, and the something else may not be passed in the same way as a
char *. That is almost certainly the cause of the behaviour you're
seeing. If a real float were passed to printf, it would be promoted to
a double, and printf will access a double-sized object at the location
where you have passed only a char *-sized one.

Your char * member works (because it *is* a char *). Your int member
happens to work because int is the same size as char * (and it
wouldn't have been promoted to anything, being an int). And your char
member works because you forgot the % sign and the char happens to be
'c':
struct foo f = { 'c', 2.34f, 100, "hello, world" };

printf_member(&f, offsetof(struct foo, c), "[c]\n");
^ should be %c

The only way to get this to work is to pass printf an argument of the
right type. Since you have to pass in something indicating the right
format for the type, you could instead pass some indication of the
type (e.g. using an enum) and then have a switch in printf_member that
covers all the possible cases.

-- Richard
 
M

Michael B Allen

The only way to get this to work is to pass printf an argument of the
right type. Since you have to pass in something indicating the right
format for the type, you could instead pass some indication of the
type (e.g. using an enum) and then have a switch in printf_member that
covers all the possible cases.

Drat! Ok. I'll have to use a big switch. I have the type information in
a parallel 'cols' array (stored with the format specifier). I was just
exercising my minimalist instincts.

Thanks for the quick answers as always,
Mike
 
P

Pramod Subramanyan

Drat! Ok. I'll have to use a big switch.

Not really, look at this

#include <stdio.h>

struct foo {
int i; char c; char* s; double f; long l;
} f = { 848, '~', "Stuff to be said", 4.5, 7474l };

print_1(char* fmt, int* data) {
return printf(fmt, *data);
}

print_2(char* fmt, double* data) {
return printf(fmt, *data);
}
int main() {
print_1("%d", &f.i); putchar('\n');
print_1("%c", &f.c); putchar('\n');
print_1("%s", &f.s); putchar('\n');
print_2("%f", &f.f); putchar('\n');
print_2("%ld", &f.l); putchar('\n');

return 0;
}

The point is that ints,chars,char* and the like are passed in one word
(on 32 bit machines) and longs, double etc. are passed as two words. So
you can use one function for the 32 bit data or lesser sized data and
another for the 64 bit sized data.

The problem here is float. Float is actually 32 bit, put is passed as a
double (64 bit). So you can't use either function for floats. Maybe you
could add a third function and then the size of your switch would be
reduced quite a bit.
 
R

Richard Tobin

Pramod Subramanyan said:
print_1("%s", &f.s); putchar('\n');
print_2("%f", &f.f); putchar('\n');

That doesn't help much, because you have to know which function to call.

In any case, it's not usually wise to rely on knowing the size of
the datatypes. Especially if you're wrong about it:
The point is that ints,chars,char* and the like are passed in one word
(on 32 bit machines) and longs, double etc. are passed as two words.

Long is 32 bits on most 32-bit processors.

In any case, the big switch is not very big. There aren't that many
built-in datatypes (assuming you don't need to handle pointers other
than strings).

-- Richard
 
P

Pramod Subramanyan

That doesn't help much, because you have to know which function to call.

1) I've heard of an operator called sizeof. Our learned friend here
perhaps needs to revisit the basics.
2) Moreover, even though our learned friend is not the person asking
question, he has however arbitrarily and pre-emptorily appointed him
sole and final judge of the usefulness of the solution. On what basis,
may I ask?
In any case, it's not usually wise to rely on knowing the size of
the datatypes. Especially if you're wrong about it:
Long is 32 bits on most 32-bit processors.

Its 64 bits on Intel based platforms, which form the biggest chunk of
the market by a LONG (pun unintended) margin, hence the "most". Anyway,
won't a sizeof solve that problem as well?

P.S: Richard, you need to do some REAL programming. Portability issues
aren't as simple as not using the sizes of datatypes. In fact, they're
one of the simplest problems to solve.
 
K

Keith Thompson

Pramod Subramanyan said:
Its 64 bits on Intel based platforms, which form the biggest chunk of
the market by a LONG (pun unintended) margin, hence the "most". Anyway,
won't a sizeof solve that problem as well?

Type long is 32 bits in every implementation I've see for 32-bit Intel
platforms. I've seen 64-bit longs on 64-bit Intel platforms. (Type
long long is, of course, required to be at least 64 bits.)

And, Pramod, please don't snip attributions. I had to manually add
the "Richard Tobin .. writes" above.
 
P

Pramod Subramanyan

Keith said:
Type long is 32 bits in every implementation I've see for 32-bit Intel
platforms. I've seen 64-bit longs on 64-bit Intel platforms. (Type
long long is, of course, required to be at least 64 bits.)

VC++, BC++, LCC and a whole bunch of other platforms on Win32 have 64
bit longs. Given that Intel has something like 85% of the PC-Processor
market, and Windows is used on 80% of those, I stick to my stand. [I
may be wrong about the exact numbers, but they're certainly in the same
region.] And may I add that you haven't seen too many implementations?

Moreover, the whole concept 32/64bit processors is ambiguous. What are
you talking about? Data bus widths, register sizes, memory addressing
capability, or instruction pointer (or program counter if you like)
widths? Simply calling a processor 32bit or a platform 64bit does not
give any real information.
 
K

Keith Thompson

Pramod Subramanyan said:
Keith said:
Type long is 32 bits in every implementation I've see for 32-bit Intel
platforms. I've seen 64-bit longs on 64-bit Intel platforms. (Type
long long is, of course, required to be at least 64 bits.)

VC++, BC++, LCC and a whole bunch of other platforms on Win32 have 64
bit longs. Given that Intel has something like 85% of the PC-Processor
market, and Windows is used on 80% of those, I stick to my stand. [I
may be wrong about the exact numbers, but they're certainly in the same
region.] And may I add that you haven't seen too many implementations?

I've seen many implementations; few of them run under Windows.
I believe you're mistaken.

Under lcc-win32, long is 32 bits. The following program:

#include <stdio.h>
#include <limits.h>

int main(void)
{
printf("short is %d bits\n", sizeof(short) * CHAR_BIT);
printf("int is %d bits\n", sizeof(int) * CHAR_BIT);
printf("long is %d bits\n", sizeof(long) * CHAR_BIT);
return 0;
}

prints

short is 16 bits
int is 32 bits
long is 32 bits

I get the same output using gcc under Cygwin under Windows XP.

The system in question has an Intel Pentium M processor (IA-32).

Can you demonstrate, using the output of the above program, a C
implementation on a 32-bit Intel processor (define the term "32-bit"
as you like) that has 64-bit long? Please specify the platform, the
compiler, and the actual output of the program.
Moreover, the whole concept 32/64bit processors is ambiguous. What are
you talking about? Data bus widths, register sizes, memory addressing
capability, or instruction pointer (or program counter if you like)
widths? Simply calling a processor 32bit or a platform 64bit does not
give any real information.

Agreed.

These details are only peripherally topical; the point is that
assuming particular sizes for the predefined types is unwise.
 
Z

Zoran Cutura

Pramod Subramanyan said:
Keith said:
Type long is 32 bits in every implementation I've see for 32-bit Intel
platforms. I've seen 64-bit longs on 64-bit Intel platforms. (Type
long long is, of course, required to be at least 64 bits.)

VC++, BC++, LCC and a whole bunch of other platforms on Win32 have 64
bit longs. Given that Intel has something like 85% of the PC-Processor
market, and Windows is used on 80% of those, I stick to my stand. [I
may be wrong about the exact numbers, but they're certainly in the same
region.] And may I add that you haven't seen too many implementations?

When I compile the following little prog with VSC++ 6.0

#include <limits.h>
#include <stdio.h>

int main(void)
{

printf("bits in char == %d\n", CHAR_BIT);
printf("size of long == %d\n", (int)sizeof(long));
return 0;
}

I get the follwing output:

bits in char == 8
size of long == 4

Which tells me, that long has 4 * 8 Bits which according to Adam Riese would be
32. But I may be wrong.
Moreover, the whole concept 32/64bit processors is ambiguous. What are
you talking about? Data bus widths, register sizes, memory addressing
capability, or instruction pointer (or program counter if you like)
widths? Simply calling a processor 32bit or a platform 64bit does not
give any real information.

You where talking about the size of longs, wheren't you? I suppose that
even on 64 Bit-Architectures longs may be 32 bits wide.
 
F

Flash Gordon

Pramod said:
VC++, BC++, LCC and a whole bunch of other platforms on Win32 have 64
bit longs.

I can't comment on BC++ or LCC, but you are wrong about VC++. I know
because I have it installed. To quote from the Fundamental Types page of
the help:

| Sizes of Fundamental Types
|
| Type Size
| bool 1 byte
| char, unsigned char, signed char 1 byte
| short, unsigned short 2 bytes
| int, unsigned int 4 bytes __intn 1, 2, 4, or 8
| bytes depending on the value of
| n. __intn is Microsoft-specific.
| long, unsigned long 4 bytes
| float 4 bytes
| double 8 bytes
| long double(1) 8 bytes
| long long Equivalent to __int64.
|
| (1) The representation of long double and double is identical.
| However, long double and double are separate types.

So unless you are claiming the CHAR_BIT is something other than 8, long
is definitely 32 bits.
> Given that Intel has something like 85% of the PC-Processor
market, and Windows is used on 80% of those, I stick to my stand. [I
may be wrong about the exact numbers, but they're certainly in the same
region.] And may I add that you haven't seen too many implementations?

I would not be surprised if Keith has seen rather more implementations
that you.
Moreover, the whole concept 32/64bit processors is ambiguous. What are
you talking about? Data bus widths, register sizes, memory addressing
capability, or instruction pointer (or program counter if you like)
widths? Simply calling a processor 32bit or a platform 64bit does not
give any real information.

Since it was Intel processors that were referred to, the most probable
definition is whether Intel claim them to be 32 or 64 bit processors.
 
R

Richard Tobin

That doesn't help much, because you have to know which function to call.

1) I've heard of an operator called sizeof.[/QUOTE]

The original poster seemed to want a single function call that worked
for all built-in types. Your proposal didn't do that. If you can fix
it up using sizeof, that might be useful.

The rest of your post consists of insults and a factual error which
others have corrected, so I will not address it.

-- Richard
 
K

Keith Thompson

Pramod Subramanyan said:
Sorry, I got this wrong.

Richard, I apologize for the patronizing tone.

Since Pramod didn't provide context, I'll mention that what he got
wrong was his claim that type long is commonly 64 bits on 32-bit Intel
platforms.
 
R

Richard Bos

Pramod Subramanyan said:
Sorry, I got this wrong.

Richard, I apologize for the patronizing tone.

Got _what_ wrong? _What_ patronising tone? I don't recall you being
patronising to me... oh, wait, maybe you were patronising to Mr.
Heathfield?

It _is_ possible to get Google Groups Broken Beta to quote, you know.

Richard
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top