printf() and void *

P

Peter Nilsson

Barry Schwarz said:
Peter said:
 %s requires a char*, %d requires an [int], etc.

Interesting that your reader didn't quote what I wrote.

My bad. Let me start afresh...

Barry Schwarz said:
Different types of object pointers are allowed to have
different sizes and different representations. But you
are allowed to convert (explicitly or implicitly) between
any of these types and void* with no loss of information.

scanf must accept many different kinds of pointers.
%s requires a char*, %d requires an int*, etc.

Currently yes.
Therefore,

This is the key word for me.
it is not possible to have a rule that says any pointer
in the variadic portion of the argument list would be
"promoted" to void*.

In conjunction with the current rules of %d expecting int *,
quite so. But that isn't to say that the automatic promotion
to void * could not work in principle: %s, %d, etc... would
simply take a void *. The original argument expression would
naturally have to be of the correct type though.
(There is such a rule that says short and char are
promoted to int and float is promoted to double in the
variadic portion of an argument list.) [scanf could
have been designed to accept void pointers across the
board

Indeed. This could apply to any pointer argument supplied
as the variadic component to variadic function.
but then compilers could not optionally check the
arguments against the conversion specifications.]

Why would _that_ be an issue?

For starters, most compilers that I've used don't bother;
secondly, compilers already have to do the static type
analysis to implement default argument promotion anyway.
Rather than provide printf with a slew of conversion
specifications (which could have also allowed function
pointers to be printed),

Just one would suffice. [Accepting say void (*)(void).]
%p is used for all object pointers after they have been
converted to void*. Since there is no automatic conversion
to void* (for the variadic portion of an argument list),
you must direct the compiler to perform the conversion by
casting the value.

What I read (perhaps hastily) was that this automatic
conversion was not (could never have been) a possible option
for the C language.

I wouldn't want it to be, but I can't see any principle
reason for it not working.
 
Y

ymuntyan

[the] example prints the representation of a function pointer,
not its value.
It's value is determined by the representation.
Of course, but that's true for all types. Should we start printing 1.01
as a series of bytes too, then?
Isn't it what "binary file formats" is about?

I meant printing the series of bytes as hexadecimal numbers, which is not
what binary file formats are about.

Attach any file to an email and you get that. I meant,
dumping bytes in hex format isn't necessarily a bad idea,
even for 1.01 (but "binary" wasn't the right word here,
true).
The standard states that "the value of the pointer is converted to a
sequence of printing characters, in an implementation-defined manner."
This, by my reading, does not allow anything other than the value of the
pointer to have an effect on the output. In particular, it does not allow
the representation of the pointer (when multiple representations of the
same value exist) to have an effect.

I am not sure if what you're saying is right (why can't an
implementation
print the "padding" bits from the pointer object you're passing to it,
even if there are those "padding" bits which may be set in different
ways without changing the value?), but it's certainly true that
"""You can not be sure that you'll get two identical strings from
printf("%p\n%p\n", p, p)"""
It may attach a time stamp to the pointer, or print some random
bits from /dev/random or whatever.

Yevgen
 
D

David Tiktin

David Tiktin said:


Right - the technique is portable, but the value isn't. :)



Again, the technique is portable, but the value isn't. Consider:

#include <stdio.h>

int main(void)
{
FILE *fp = fopen("foo.txt", "w");
if(fp != NULL)
{
if(fputs("A\n", fp) != EOF && fclose(fp))
{
puts("All is well.");
}
}
return 0; /* if fputs failed, the stream will be closed on exit!
*/
}

If "All is well" is displayed (under any conforming hosted
implementation), we have created a text file, which in C terms
contains one line comprising 'A' and a newline character. So the
technique is portable.

But what have we actually created, in what we might call absolute
terms? On a PC under Windows, the file will contain three bytes,
with values 65, 13, and 10 (obviously I'm using decimal
representation here). On an old-style Mac, it would contain just
two bytes: 65, 13. On a Linux box (or, I *think*, a modern Mac),
it would contain 65, 10. On an IBM mainframe, well, don't ask(!),
but to start off with, that 'A' would be 193 rather than 65. The
point is that, whilst the output can be interpreted consistently
within the machine/OS/implementation combination, moving that file
to another (disparate) system will (or at least may) result in
that interpretation becoming invalidated unless some kind of data
massage is performed.

Yes, this is exactly what I had in mind, which is why I brought up
this issue. The effect is "implementation defined," not undefined.
In your reply to the OP, you seemed to be saying that portable code
can't rely on implementation defined behavior. That seemed to me too
strong.
So I guess it all depends on what we mean by "portable"!

But *do* we mean by "portable code," code that, among other things,
doesn't rely on any implementation defined behavior? I'm saying that
it couldn't mean that (even in part) or all (or almost all?) I/O
would be non-portable. That doesn't seem right to me. One of the
reasons I like C is because it has portable I/O libraries. I don't
have to #ifdef my code if use stdio.h defined functions. So why
would you say that printing the value of a data pointer using %p is
not portable? It can't just be that the output is implementation
defined.

As an operational definition, I would say portable code is code there
is no reason to conditionally compile based on the platform the code
will run on. You won't see portable code surrounded by:

#if defined(_WIN32)
...
#elif defined(_LINUX)
...
#else
#error "Unknown platform."
#endif

although portable code may make use of macros #defined in sections
such as this. (The conditionally compiled sections may make other
sections of code portable.)

By this definition, printing the value of a pointer with %p is
portable, as it using the Standard Library functions to read and
write text files. If you make certain assumptions about what will
happen in these cases, other parts of your code may become non-
portable, for instance, if you assume that %p will print exactly 8
hex digits on all platforms. But the presence of implemenation
defined behavior by itself doesn't make using %p automatically non-
portable.

Dave
 
R

Richard Heathfield

David Tiktin said:

But *do* we mean by "portable code," code that, among other things,
doesn't rely on any implementation defined behavior?

In comp.lang.c we are very quick to say that such-and-such is, or is not,
portable, but in practice portability might be considered a continuum (in
several different dimensions) rather than a binary attribute of a given
code construct.

Code that doesn't rely on /any/ implementation-defined (or unspecified or
undefined) behaviour - that is, what the Standard calls a "strictly
conforming program" - is maximally portable, but even such "ideal" code
depends on the very characteristics of the platform that support its
ability to produce output (if indeed it does produce output, which need
not apply to library code, of course).

In practice, code can be *very* portable without necessarily being strictly
conforming. For example, the assumption that CHAR_BIT is 8 works on a
goodly selection of platforms (although of course not everywhere). If a
program depends for its correct working on the (presumably asserted or
otherwise validated) assumption that CHAR_BIT is 8, but is otherwise free
of implementation-defined behaviour, is it portable or not? Answer: it's
portable to platforms on which the assumption is valid, and not to those
on which it is not valid - so the program is portable *to a degree*.

Although it is certainly true that relying on the implementation-defined
behaviour of <foo> renders your code non-portable, this is only in an
absolute sense. It is very likely that the code will still work just fine
on a large number of platforms.

In the case in point, however, which is far more boring than you're giving
it credit for, I was only talking about the value being non-portable, in
the sense that you're not guaranteed to get the same output for the same
program on two different platforms (or even for two prints of the same
pointer value on the same program within the same run). I'd have thought
this was self-evidently true, and it's also mind-numbingly dull. :)
 
M

Morris Dovey

Richard said:
Code that doesn't rely on /any/ implementation-defined (or unspecified or
undefined) behaviour - that is, what the Standard calls a "strictly
conforming program" - is maximally portable, but even such "ideal" code
depends on the very characteristics of the platform that support its
ability to produce output (if indeed it does produce output, which need
not apply to library code, of course).

Erm, yes. Thank you for making that so clear.

ROFL
 
H

Harald van Dijk

I am not sure if what you're saying is right (why can't an
implementation
print the "padding" bits from the pointer object you're passing to it,
even if there are those "padding" bits which may be set in different
ways without changing the value?), but it's certainly true that """You
can not be sure that you'll get two identical strings from
printf("%p\n%p\n", p, p)"""
It may attach a time stamp to the pointer, or print some random bits
from /dev/random or whatever.

On such an implementation, what's converted to a sequence of printing
characters is not the value of the pointer. It's the value of the pointer,
plus part of the representation and/or random bits. I consider that in the
same category as why putchar('e') isn't allowed to print "hello", even
though printing "hello" would also have caused 'e' to be written.
 
Y

ymuntyan

On such an implementation, what's converted to a sequence of printing
characters is not the value of the pointer. It's the value of the pointer,
plus part of the representation and/or random bits. I consider that in the
same category as why putchar('e') isn't allowed to print "hello", even
though printing "hello" would also have caused 'e' to be written.

Suppose it's a PC with 32-bit pointers, without multiple
representations
for the same value, where cast to unsigned int and back is well-
defined.
The following function would be a conforming way to print void* in
printf:

void print_pointer(void *p)
{
static unsigned counter;
printf("'''%08x%08x'''", (unsigned)p, counter++);
}

It prints "extra" here, yes. But that "extra" is not forbidden.
I have no idea why one would do that, but it certainly would
be conforming. How is appending a number worse than prepending
"0x" or printing "(nil)"? fputc() is different, it doesn't have
this "implementation-defined manner" freedom. It "writes the
character", not a sequence of characters obtained in an
implementation-defined manner.

Whether it's allowed to use the bits from the representation,
I don't know. I'd think it is, but it's a different story
anyway.

Yevgen
 
H

Harald van Dijk

Suppose it's a PC with 32-bit pointers, without multiple representations
for the same value, where cast to unsigned int and back is well-
defined.
The following function would be a conforming way to print void* in
printf:

void print_pointer(void *p)
{
static unsigned counter;
printf("'''%08x%08x'''", (unsigned)p, counter++);
}

It prints "extra" here, yes. But that "extra" is not forbidden. I have
no idea why one would do that, but it certainly would be conforming. How
is appending a number worse than prepending "0x"

In your example, what's printed depends on more than the pointer value.
The pointer value is what must be formatted. Not the pointer value and
some other data.
or printing "(nil)"?

That's fine. It depends only on the value of the pointer, nothing else.
fputc() is different, it doesn't have this "implementation-defined
manner" freedom.

And the *printf functions have no freedom in choosing what data to use to
print pointers. They happen to have freedom in choosing how to use that
data, but that's a different issue.
It "writes the character", not a sequence of characters
obtained in an implementation-defined manner.

Right. The character is printed. Not the character and some other data.

If putchar('e') prints "hello", it has written the character. It also
happened to have written other data, but the description of putchar
doesn't say it can't.

It's clear to both of us this logic is invalid.

If printf("%p", p) includes a random number in its output, it has
formatted the pointer value. It also happened to have formatted other
data, but the description of fprintf doesn't say it can't.

I don't seem to be able to convince you this logic is equally invalid.
 
Y

ymuntyan

In your example, what's printed depends on more than the pointer value.
The pointer value is what must be formatted. Not the pointer value and
some other data.


That's fine. It depends only on the value of the pointer, nothing else.

And also on the will of implementor who can write "(nil)" or
"0" or what else he likes. Anyway, you claim that the format
of %p output must not depend on the program state; I claim it
may depend on the program state. E.g. implementation could
choose the format on startup, like print "(nil)" if program
started on Monday and print "0" if program started on other
day. Or print "(nil)" when printf is *called* on Monday. Or
append a random number.

I guess we can just agree to disagree here :)
And the *printf functions have no freedom in choosing what data to use to
print pointers. They happen to have freedom in choosing how to use that
data, but that's a different issue.


Right. The character is printed. Not the character and some other data.

If putchar('e') prints "hello", it has written the character. It also
happened to have written other data, but the description of putchar
doesn't say it can't.

Description of files says it can't. What's written can be read
back and you'll know that more than one or a wrong character was
written. If it can't be read, then there is simply nothing to
talk about.
It's clear to both of us this logic is invalid.

If printf("%p", p) includes a random number in its output, it has
formatted the pointer value. It also happened to have formatted other
data, but the description of fprintf doesn't say it can't.

So printf("%p") *is* different in this respect from putchar().

Yevgen
 
H

Harald van Dijk

Description of files says it can't. What's written can be read back and
you'll know that more than one or a wrong character was written. If it
can't be read, then there is simply nothing to talk about.

If stdout hasn't been redirected (either by the program or the
environment), it usually can't be read back, yet I'm quite sure that
implementations aren't allowed to make putchar('e') print "hello" even if
they can determine with absolute certainty that stdout is not by the
program.
 
H

Harald van Dijk

If stdout hasn't been redirected (either by the program or the
environment), it usually can't be read back, yet I'm quite sure that
implementations aren't allowed to make putchar('e') print "hello" even
if they can determine with absolute certainty that stdout is not by the
readable ^

Sorry about that.
 
C

CBFalconer

Richard said:
CBFalconer said:
Barry said:
...
scanf must accept many different kinds of pointers.
%s requires a char*, %d requires an [int], etc.

Interesting that your reader didn't quote what I wrote.

Hm. According to mine, it did.

<sigh> Read it again, Chuck.

All I can see is that he truncated it. Tain't extremely
important. Ahhh - int* became [int].
 

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