printf("%hd\n", 42);

73 views
Skip to first unread message

Keith Thompson

unread,
Jan 23, 2007, 5:04:55 PM1/23/07
to
Does
printf("%hd\n", 42);
invoke undefined behavior?

C99 7.19.6.1p7, describing the 'h' length modifier, says it

Specifies that a following d, i, o, u, x, or X conversion
specifier applies to a short int or unsigned short int argument
(the argument will have been promoted according to the integer
promotions, but its value shall be converted to short int or
unsigned short int before printing); or that a following n
conversion specifier applies to a pointer to a short int argument.

p9 says:

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

Paragraph 7 seems to imply that the "correct type" for "%hd" is short,
so passing an argument of type int would invoke undefined behavior.
But a short argument is going to be promoted to int anyway.

Is there any difference, as far as the standard is concerned, between
printf("%hd\n", 42);
and
printf("%hd\n", (short)42);
? There would be no difference if printf() were replaced with a
user-written variadic function that uses <stdarg.h> to process its
arguments.

Conceivably an implementation could treat printf() and friends
specially somehow. This special treatment would have to survive
storing the address of the printf function in a pointer and calling it
indirectly. Such an implementation would have to go through some
rather silly contortions for no particular benefit.

Even assuming that printf("%hd\n", 42) invokes UB, an implementation
can still conform by treating it exactly like printf("%hd\n", (short)42).
But programs intended to be portable must avoid certain calls that are
almost certain to work properly in any real-world implementation.

The simplest way to resolve this would be to assert that the "correct
type" for "%hd" is int, not short; more generally, the "correct type"
for any conversion specification is the promoted type. That's a
slightly strained reading of the current wording, but IMHO it's not
entirely an unreasonable one.

--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
We must do something. This is something. Therefore, we must do this.

kuy...@wizard.net

unread,
Jan 23, 2007, 6:08:11 PM1/23/07
to

The fundamental issue you're raising has been brought up before, in a
different context. Here's my take on the issue: It's certainly the case
that printf() is required to use a function-call protocol compatible
with that which could be used by a user-defined equivalent that uses
<stdarg.h>; otherwise function pointers wouldn't work right. However,
printf() doesn't have to actually use <stdarg.h>, or even be written in
C. The function call protocol could carry information about the
original type, prior to promotion, even though this information is not
accessible through the standard-defined features of <stdarg.h>, so long
as it doesn't prevent those features from behaving in the fashion
described by the standard. As long as the behavior is otherwise well
defined, whatever method printf() uses to retrieve parameter values
must be equivalent to the standard-defined behavior of <stdarg.h>
features.

However, because paragraph 7 refers to the type of the argument,
rather than the promoted type of the argument, printf() is allowed to
make use of that extra type information; if the pre-promotion type
doesn't match the corresponding element of the format string, the
behavior is undefined, regardless of whether or not the promoted type
of the argument matches the promoted type corresponding to the one
specified by the format string. I seriously doubt that this was
intentional or desireable, but such behavior would conform to the
actual words of the standard. This interpretation was vigorously
disputed by some people.

Keith Thompson

unread,
Jan 23, 2007, 7:11:47 PM1/23/07
to
kuy...@wizard.net writes:
[...]

> The fundamental issue you're raising has been brought up before, in a
> different context. Here's my take on the issue: It's certainly the case
> that printf() is required to use a function-call protocol compatible
> with that which could be used by a user-defined equivalent that uses
> <stdarg.h>; otherwise function pointers wouldn't work right.
[...]

An overly pedantic quibble: An implementation *could* generate code
that, on each attempt to call a function through a pointer with a type
compatible with that of printf(), check whether it actually is printf,
and if so, call using some magical calling convention.

For example in this program:

#include <stdio.h>
int main(void)
{
int (*ptr)(const char *restrict, ...) = printf;
ptr("%hd\n", 42);
return 0;
}

the call

ptr("%hd\n", 42);

could be replaced by the compiler with code equivalent to this:

if (ptr == printf) {


printf("%hd\n", 42);
}

else {
ptr("%hd\n", 42);
}

And likewise for all the other standard *printf() functions.

I suspect the mythical DS9K does this (and literally causes demons to
fly out your nose), but for any other implementation it would be a lot
of work for no real benefit.

> However, because paragraph 7 refers to the type of the argument,
> rather than the promoted type of the argument, printf() is allowed to
> make use of that extra type information; if the pre-promotion type
> doesn't match the corresponding element of the format string, the
> behavior is undefined, regardless of whether or not the promoted type
> of the argument matches the promoted type corresponding to the one
> specified by the format string. I seriously doubt that this was
> intentional or desireable, but such behavior would conform to the
> actual words of the standard. This interpretation was vigorously
> disputed by some people.

I can imagine!

Is there a DR on this issue? (I looked, but couldn't find one.)

Jun Woong

unread,
Jan 23, 2007, 8:05:50 PM1/23/07
to

Keith Thompson wrote:
> kuy...@wizard.net writes:[...]> The fundamental issue you're raising has been brought up before, in a
> > different context. Here's my take on the issue: It's certainly the case
> > that printf() is required to use a function-call protocol compatible
> > with that which could be used by a user-defined equivalent that uses
> > <stdarg.h>; otherwise function pointers wouldn't work right.[...]
>
> An overly pedantic quibble: An implementation *could* generate code
> that, on each attempt to call a function through a pointer with a type
> compatible with that of printf(), check whether it actually is printf,
> and if so, call using some magical calling convention.
>
[an example for the mythical DS9K snipped]

>
> I suspect the mythical DS9K does this (and literally causes demons to
> fly out your nose), but for any other implementation it would be a lot
> of work for no real benefit.
>

Or what about this? I'm not talking about an imaginary implementation,
but a real practical implementation. If one takes the wording in
question as to mean "take an int that is the promoted type of short
and convert it to short before printing," the following should print
out (short)(SHRT_MAX+1).

printf("%hd", SHRT_MAX+1);

But, (at least) one version of glibc gave just (SHRT_MAX+1). For
example, it printed out

32768
-32768

for

printf("\n%hd\n", SHRT_MAX+1);
printf("%hd\n", (short)(SHRT_MAX+1));

where SHRT_MAX == 32767 and INT_MAX == 2**31-1.

> > However, because paragraph 7 refers to the type of the argument,
> > rather than the promoted type of the argument, printf() is allowed to
> > make use of that extra type information; if the pre-promotion type
> > doesn't match the corresponding element of the format string, the
> > behavior is undefined, regardless of whether or not the promoted type
> > of the argument matches the promoted type corresponding to the one
> > specified by the format string. I seriously doubt that this was
> > intentional or desireable, but such behavior would conform to the
> > actual words of the standard. This interpretation was vigorously

> > disputed by some people.I can imagine!


>
> Is there a DR on this issue? (I looked, but couldn't find one.)
>

Nope. That (I mean we have no DR on that) is the reason why we
discuss this periodically. The first one I know of was done by one of
the glibc(?) developers. He insisted there that the standard (C90)
required the argument corresponding to %hd be really of type "short,"
which explains the behavior of the example above. If you have a plan
to file a DR for this issue, I ask you to cover a similar issue like

char *p = &some_char_object;
printf("%p\n", p);

and, considering the behavior of %hd in *scanf, to propose revising
the problematic wording to something like "the type of an argument
for %hd should be /int/ but its value should be representable with
/short/," which endorses the above implementation.


--
Jun, Woong (woong at icu.ac.kr)
Samsung Electronics Co., Ltd.

``All opinions expressed are mine, and do not represent
the official opinions of any organization.''

Keith Thompson

unread,
Jan 23, 2007, 8:32:07 PM1/23/07
to
"Jun Woong" <wo...@icu.ac.kr> writes:
> Keith Thompson wrote:
> > kuy...@wizard.net writes:[...]
> > > The fundamental issue you're raising has been brought up before, in a
> > > different context. Here's my take on the issue: It's certainly the case
> > > that printf() is required to use a function-call protocol compatible
> > > with that which could be used by a user-defined equivalent that uses
> > > <stdarg.h>; otherwise function pointers wouldn't work right.[...]
> >
> > An overly pedantic quibble: An implementation *could* generate code
> > that, on each attempt to call a function through a pointer with a type
> > compatible with that of printf(), check whether it actually is printf,
> > and if so, call using some magical calling convention.
[snip]

> Or what about this? I'm not talking about an imaginary implementation,
> but a real practical implementation. If one takes the wording in
> question as to mean "take an int that is the promoted type of short
> and convert it to short before printing," the following should print
> out (short)(SHRT_MAX+1).
>
> printf("%hd", SHRT_MAX+1);

Where the value of (short)(SHRT_MAX+1) is implementation-defined (or
it can raise an implementation-defined signal). But I suppose the
result has to be consistent. (Or does it?)

> But, (at least) one version of glibc gave just (SHRT_MAX+1). For
> example, it printed out
>
> 32768
> -32768
>
> for
>
> printf("\n%hd\n", SHRT_MAX+1);
> printf("%hd\n", (short)(SHRT_MAX+1));
>
> where SHRT_MAX == 32767 and INT_MAX == 2**31-1.

It's possible that the code that implements "%hd" just ignores the
'h'. This gives valid results for any argument of type short. It
depends, I think, on passing an int value being undefined behavior, as
the standard seems to imply.

For the second call, the conversion to short is done in user code, and
yields an implementation-defined value that happens to be -32768.

If the standard said that "%hd" expects an argument of type int, which
it converts to short, then glibc's behavior would be non-conforming.

But the glibc implementation *could* satisfy the both the literal
wording of the standard, and the "sensible" interpretation that I
advocate, by converting the int argument to short inside printf().

[...]

> > Is there a DR on this issue? (I looked, but couldn't find one.)
> >
>
> Nope. That (I mean we have no DR on that) is the reason why we
> discuss this periodically. The first one I know of was done by one of
> the glibc(?) developers. He insisted there that the standard (C90)
> required the argument corresponding to %hd be really of type "short,"
> which explains the behavior of the example above. If you have a plan
> to file a DR for this issue, I ask you to cover a similar issue like
>
> char *p = &some_char_object;
> printf("%p\n", p);
>
> and, considering the behavior of %hd in *scanf, to propose revising
> the problematic wording to something like "the type of an argument
> for %hd should be /int/ but its value should be representable with
> /short/," which endorses the above implementation.

I don't think the word "should" is appropriate; it says nothing about
what happens if it *isn't* of type short.

I see three options:

1. The argument must be of type short. Passing an int, even if it's
within the range of short, invokes undefined behavior. This is what
the current wording *seems* to imply. My opinion: Ick.

2. The argument is of type int. It is converted to short by printf.

3. The argument is of type int. If the argument cannot be represented
in type short, the behavior is undefined.

I have no strong preference between options 2 and 3. An advantage of
option 3 is that all existing implementations are likely to remain
conforming.

Yet another issue is whether this:
printf("%d\n", 42U);
invokes undefined behavior.

As for a DR, I don't know how to file one, or even whether I'd be able
to do so myself.

Douglas A. Gwyn

unread,
Jan 24, 2007, 1:42:22 AM1/24/07
to
"Jun Woong" <wo...@icu.ac.kr> wrote ...

> printf("\n%hd\n", SHRT_MAX+1);
> printf("%hd\n", (short)(SHRT_MAX+1));

Those both are undefined behavior, the only real difference
being that the first instance runs into trouble when it does the
required internal conversion (casting a too-large value to short)
while the second instance feeds printf God knows what due to
already having triggered the same kind of undefined behavior
before printf is even invoked.

> ,,, one of the glibc(?) developers. He insisted there that the standard

> (C90)
> required the argument corresponding to %hd be really of type "short,"
> which explains the behavior of the example above.

How could it? The argument clearly must be promoted to int.
If printf sees a difference, there must be a difference in the
promoted argument values.


Douglas A. Gwyn

unread,
Jan 24, 2007, 1:56:14 AM1/24/07
to
"Keith Thompson" <ks...@mib.org> wrote ...

> 1. The argument must be of type short. Passing an int, even if it's
> within the range of short, invokes undefined behavior. This is what
> the current wording *seems* to imply. My opinion: Ick.
> 2. The argument is of type int. It is converted to short by printf.
> 3. The argument is of type int. If the argument cannot be represented
> in type short, the behavior is undefined.

(2) was intended. There is an explicit requirement for an internal
conversion to short (and the library I maintain does that). But also,
the *expected* (not required) usage model was that the
unpromoted argument would have type short; due to value-
preserving rules the internal conversion would then accomplish
nothing. Since the spec does say that the argument is required
to have type short, a program that uses any other type in that
context is not strictly conforming, but if the compiler accepts
the program I would say that it has to pass the value as an int
and printf has to convert it (using C typecast rules) to short
before formatting the text representing the result.

P.S. I mistakenly said in a nearby posting that conversion of an
overly-large value into short was u.b. whereas Keith correctly
notes that C99 says it's impl.-def. (or could generate a signal!).
Clearly you don't normally want your program to depend on
such behavior, so that is a situation you should avoid in the
first place.


Keith Thompson

unread,
Jan 24, 2007, 2:34:37 AM1/24/07
to
"Douglas A. Gwyn" <DAG...@null.net> writes:
> "Jun Woong" <wo...@icu.ac.kr> wrote ...
> > printf("\n%hd\n", SHRT_MAX+1);
> > printf("%hd\n", (short)(SHRT_MAX+1));
>
> Those both are undefined behavior, the only real difference
> being that the first instance runs into trouble when it does the
> required internal conversion (casting a too-large value to short)
> while the second instance feeds printf God knows what due to
> already having triggered the same kind of undefined behavior
> before printf is even invoked.

There is no undefined behavior in the second instance. The value
SHRT_MAX+1, which is of type int, is converted to short; the
conversion either yields an implementation-defined result or raises an
implementation-defined signal. Assuming no signal is raised, the
behavior of printf() is well defined for any possible result.

Keith Thompson

unread,
Jan 24, 2007, 2:45:51 AM1/24/07
to
Keith Thompson <ks...@mib.org> writes:
> "Douglas A. Gwyn" <DAG...@null.net> writes:
> > "Jun Woong" <wo...@icu.ac.kr> wrote ...
> > > printf("\n%hd\n", SHRT_MAX+1);
> > > printf("%hd\n", (short)(SHRT_MAX+1));
> >
> > Those both are undefined behavior, the only real difference
> > being that the first instance runs into trouble when it does the
> > required internal conversion (casting a too-large value to short)
> > while the second instance feeds printf God knows what due to
> > already having triggered the same kind of undefined behavior
> > before printf is even invoked.
>
> There is no undefined behavior in the second instance. The value
> SHRT_MAX+1, which is of type int, is converted to short; the
> conversion either yields an implementation-defined result or raises an
> implementation-defined signal. Assuming no signal is raised, the
> behavior of printf() is well defined for any possible result.

As Doug acknowledged in a followup he posted before I wrote the above,
but I hadn't read yet.

Jun Woong

unread,
Jan 24, 2007, 3:08:01 AM1/24/07
to

Keith Thompson wrote:
> "Jun Woong" <w...@icu.ac.kr> writes:
[...]

> > Or what about this? I'm not talking about an imaginary implementation,
> > but a real practical implementation. If one takes the wording in
> > question as to mean "take an int that is the promoted type of short
> > and convert it to short before printing," the following should print
> > out (short)(SHRT_MAX+1).
> >
> > printf("%hd", SHRT_MAX+1);
>
> Where the value of (short)(SHRT_MAX+1) is implementation-defined (or
> it can raise an implementation-defined signal). But I suppose the
> result has to be consistent. (Or does it?)
>

I believe so.

> > But, (at least) one version of glibc gave just (SHRT_MAX+1). For
> > example, it printed out
> >
> > 32768
> > -32768
> >
> > for
> >
> > printf("\n%hd\n", SHRT_MAX+1);
> > printf("%hd\n", (short)(SHRT_MAX+1));
> >
> > where SHRT_MAX == 32767 and INT_MAX == 2**31-1.
>
> It's possible that the code that implements "%hd" just ignores the
> 'h'. This gives valid results for any argument of type short. It
> depends, I think, on passing an int value being undefined behavior, as
> the standard seems to imply.
>

Exactly. the glib implementation of *printf simply ignored the 'h'
modifier. As I said, the implementer strongly claimed that was okay
based on the literal reading of the standard.

>
> But the glibc implementation *could* satisfy the both the literal
> wording of the standard, and the "sensible" interpretation that I
> advocate, by converting the int argument to short inside printf().
>

It's done by the recent versions of glibc, as I recall.

[...]


> >
> > Nope. That (I mean we have no DR on that) is the reason why we
> > discuss this periodically. The first one I know of was done by one of
> > the glibc(?) developers. He insisted there that the standard (C90)
> > required the argument corresponding to %hd be really of type "short,"
> > which explains the behavior of the example above. If you have a plan
> > to file a DR for this issue, I ask you to cover a similar issue like
> >
> > char *p = &some_char_object;
> > printf("%p\n", p);
> >
> > and, considering the behavior of %hd in *scanf, to propose revising
> > the problematic wording to something like "the type of an argument
> > for %hd should be /int/ but its value should be representable with
> > /short/," which endorses the above implementation.
>
> I don't think the word "should" is appropriate; it says nothing about
> what happens if it *isn't* of type short.
>

Ah, I should have said "shall" or "has to."

> I see three options:
>
[first and second option snipped]


>
> 3. The argument is of type int. If the argument cannot be represented
> in type short, the behavior is undefined.
>
> I have no strong preference between options 2 and 3. An advantage of
> option 3 is that all existing implementations are likely to remain
> conforming.
>

This is exactly one of the reasons I mentioned the wording rendering
values non-representable with /short/ to result in UB. It is also not
inconsistent with what the *scanf family does, which explains why
*printf has %hd, I believe.

> Yet another issue is whether this:
> printf("%d\n", 42U);
> invokes undefined behavior.
>

To be concise, narrowing the gap between the description for *printf
and that for <stdarg.h> in the standard, which was broaden by C99.

Jun Woong

unread,
Jan 24, 2007, 3:31:27 AM1/24/07
to

"Douglas A. Gwyn" wrote:
> "Jun Woong" <w...@icu.ac.kr> wrote ...

> > printf("\n%hd\n", SHRT_MAX+1);
> > printf("%hd\n", (short)(SHRT_MAX+1));
>
> Those both are undefined behavior, the only real difference
> being that the first instance runs into trouble when it does the
> required internal conversion (casting a too-large value to short)
> while the second instance feeds printf God knows what due to
> already having triggered the same kind of undefined behavior
> before printf is even invoked.
>

It has been i-d since C90.

> > ,,, one of the glibc(?) developers. He insisted there that the standard
> > (C90)
> > required the argument corresponding to %hd be really of type "short,"
> > which explains the behavior of the example above.
>
> How could it? The argument clearly must be promoted to int.
> If printf sees a difference, there must be a difference in the
> promoted argument values.

Yes, but it should be of type "short" before applying the default
promotion to it. (Huh?) That is,

printf("%hd\n", 1); /* 1 is of type int, not short */

worked well on the past versions of glibc by chance, while

printf("%hd\n", (short)1);

was guaranteed to work by the standard. Whether you agree to this or
not, that is exactly what the standard says.

Jun Woong

unread,
Jan 24, 2007, 4:07:52 AM1/24/07
to

"Douglas A. Gwyn" <DAG...@null.net> wrote:
> "Keith Thompson" <k...@mib.org> wrote ...


>
> > 1. The argument must be of type short. Passing an int, even if it's
> > within the range of short, invokes undefined behavior. This is what
> > the current wording *seems* to imply. My opinion: Ick.
> > 2. The argument is of type int. It is converted to short by printf.
> > 3. The argument is of type int. If the argument cannot be represented
> > in type short, the behavior is undefined.
>
> (2) was intended. There is an explicit requirement for an internal
> conversion to short (and the library I maintain does that).

That requirement is meaningful only when the standard allows to put
a value that is not representable with "short," which is not the case
with the current standard.

> But also,
> the *expected* (not required) usage model was that the
> unpromoted argument would have type short; due to value-
> preserving rules the internal conversion would then accomplish
> nothing. Since the spec does say that the argument is required
> to have type short, a program that uses any other type in that
> context is not strictly conforming,

Which means that (3) was intended in fact.

kuy...@wizard.net

unread,
Jan 24, 2007, 7:08:00 AM1/24/07
to
Douglas A. Gwyn wrote:
> "Jun Woong" <wo...@icu.ac.kr> wrote ...
...

> > ,,, one of the glibc(?) developers. He insisted there that the standard
> > (C90)
> > required the argument corresponding to %hd be really of type "short,"
> > which explains the behavior of the example above.
>
> How could it? The argument clearly must be promoted to int.
> If printf sees a difference, there must be a difference in the
> promoted argument values.

There's nothing prohibiting an implementation of C from using a
function call protocol that carries information about the types of the
arguments before promotion. Such information would, of necessity, be
unavailable to the called function if it is written in strictly
conforming C. However, there's no requirement that printf() be written
in strictly conforming C; there's not even a requirement that it be
written in C. It must produce the same behavior as if it handled
arguments in normal C fashion, when the behavior is defined. However,
7.19.6.1p9 explicitly says that if "the argument ..." (NOT the promoted
argument) " ... is not the correct type for the corresponding
conversion specification, the behavior is undefined". That gives
printf() the license to treat the subject line expression as an error.

Robert Gamble

unread,
Jan 24, 2007, 9:10:27 AM1/24/07
to
On Jan 24, 7:08 am, kuy...@wizard.net wrote:
> Douglas A. Gwyn wrote:
> > "Jun Woong" <w...@icu.ac.kr> wrote ...

> ...
> > > ,,, one of the glibc(?) developers. He insisted there that the standard
> > > (C90)
> > > required the argument corresponding to %hd be really of type "short,"
> > > which explains the behavior of the example above.
>
> > How could it? The argument clearly must be promoted to int.
> > If printf sees a difference, there must be a difference in the
> > promoted argument values.There's nothing prohibiting an implementation of C from using a

> function call protocol that carries information about the types of the
> arguments before promotion. Such information would, of necessity, be
> unavailable to the called function if it is written in strictly
> conforming C. However, there's no requirement that printf() be written
> in strictly conforming C; there's not even a requirement that it be
> written in C. It must produce the same behavior as if it handled
> arguments in normal C fashion, when the behavior is defined. However,
> 7.19.6.1p9 explicitly says that if "the argument ..." (NOT the promoted
> argument) " ... is not the correct type for the corresponding
> conversion specification, the behavior is undefined".

The description for the "d" conversion specifier says:
"The int argument is converted to signed decimal..."
It doesn't say "the unqualified version of the argument", would you
conclude that the following is invalid by the same logic?

#include <stdio.h>

int main (void) {
const int i = 10;
printf("%d\n", i);
return 0;
}

Robert Gamble

kuy...@wizard.net

unread,
Jan 24, 2007, 10:52:03 AM1/24/07
to
Robert Gamble wrote:
> On Jan 24, 7:08 am, kuy...@wizard.net wrote:
....

> > arguments in normal C fashion, when the behavior is defined. However,
> > 7.19.6.1p9 explicitly says that if "the argument ..." (NOT the promoted
> > argument) " ... is not the correct type for the corresponding
> > conversion specification, the behavior is undefined".
>
> The description for the "d" conversion specifier says:
> "The int argument is converted to signed decimal..."
> It doesn't say "the unqualified version of the argument", would you
> conclude that the following is invalid by the same logic?
>
> #include <stdio.h>
>
> int main (void) {
> const int i = 10;
> printf("%d\n", i);
> return 0;
> }

I don't see that as being precisely the same logic. The set of
qualified int arguments is a subset of the set of int arguments. In
order to exclude qualified int arguments, that description would have
to use a modifier like "unqualified". This isn't applicable to my
logic: the set of short arguments is not a subset of the set of int
arguments. The set of short arguments is a subset of the set of
arguments that get promoted to int, but 7.19.6.1p9 doesn't mention the
promoted type - which is precisely my point.

If I'm wrong, and in that context 'int' is in fact a synonym for
"unqualified int", then that would be an additional reason why
7.19.6.1p9 needs re-writing. If there's no need to use the phrase
"promoted type" in 7.19.6.1p9 - if that is something inherently implied
by the use of "type" in that context, then it's not clear to me that
there's any need to use term "promoted type" anywhere else in the
standard, either. The same interpretation should apply in all of those
cases, too..

Robert Gamble

unread,
Jan 24, 2007, 11:35:31 AM1/24/07
to
On Jan 24, 10:52 am, kuy...@wizard.net wrote:
> Robert Gamble wrote:
> > On Jan 24, 7:08 am, kuy...@wizard.net wrote:
> ....
> > > arguments in normal C fashion, when the behavior is defined. However,
> > > 7.19.6.1p9 explicitly says that if "the argument ..." (NOT the promoted
> > > argument) " ... is not the correct type for the corresponding
> > > conversion specification, the behavior is undefined".
>
> > The description for the "d" conversion specifier says:
> > "The int argument is converted to signed decimal..."
> > It doesn't say "the unqualified version of the argument", would you
> > conclude that the following is invalid by the same logic?
>
> > #include <stdio.h>
>
> > int main (void) {
> > const int i = 10;
> > printf("%d\n", i);
> > return 0;
> I don't see that as being precisely the same logic.

Fine. How about this example pulled at random from the Library clause
of the Standard:

"C99 - 7.4 Character Handling

The header <ctype.h> declares several functions useful for classifying
and mapping
characters.168) In all cases the argument is an int, the value of which
shall be
representable as an unsigned char or shall equal the value of the macro
EOF."

Noting that is does not say "the argument, after promotion, is an int",
consider the following fragment:

char i = 1;
isalnum(i); /* The pre-promoted type is not int, UB? */
isalpha((unsigned char)i); /* Same thing, a common usage */

What say you?

Robert Gamble

kuy...@wizard.net

unread,
Jan 24, 2007, 12:26:42 PM1/24/07
to
Robert Gamble wrote:
...

> Fine. How about this example pulled at random from the Library clause
> of the Standard:
>
> "C99 - 7.4 Character Handling
>
> The header <ctype.h> declares several functions useful for classifying
> and mapping
> characters.168) In all cases the argument is an int, the value of which
> shall be
> representable as an unsigned char or shall equal the value of the macro
> EOF."
>
> Noting that is does not say "the argument, after promotion, is an int",
> consider the following fragment:
>
> char i = 1;
> isalnum(i); /* The pre-promoted type is not int, UB? */
> isalpha((unsigned char)i); /* Same thing, a common usage */
>
> What say you?

The same as I said for printf(): by referring to the type of the
argument, rather than the promoted type of the argument, it technically
(and almost certainly unintentionally) renders the behavior of your
code undefined.

Jun Woong

unread,
Jan 24, 2007, 12:34:21 PM1/24/07
to

"Robert Gamble" <rgambl...@gmail.com> wrote:
> On Jan 24, 10:52 am, kuy...@wizard.net wrote:

[...]


> > I don't see that as being precisely the same logic.
>
> Fine. How about this example pulled at random from the Library clause
> of the Standard:
>
> "C99 - 7.4 Character Handling
>
> The header <ctype.h> declares several functions useful for classifying
> and mapping
> characters.168) In all cases the argument is an int, the value of which
> shall be
> representable as an unsigned char or shall equal the value of the macro
> EOF."
>
> Noting that is does not say "the argument, after promotion, is an int",
> consider the following fragment:
>
> char i = 1;
> isalnum(i); /* The pre-promoted type is not int, UB? */
> isalpha((unsigned char)i); /* Same thing, a common usage */
>
> What say you?
>

You are saying nothing but the fact that both, the descriptions for
printf and is*, have a defect. I am afraid you logic breaks James
Kuyper's argument.

#include <ctype.h>
...
isprint(0.0);

Do you think this results in UB or not?

If you do, then I don't see why

isprint((char)0);

does not.

If you don't, you're just saying that most implementations of is*()
are broken.

Message has been deleted

Jun Woong

unread,
Jan 24, 2007, 12:45:23 PM1/24/07
to

I wrote:
> You are saying nothing but the fact that both, the descriptions for
> printf and is*, have a defect. I am afraid you logic breaks James
> Kuyper's argument.
>

Oops! The last sentence should read as:

I am afraid your logic can't break ...

Sorry. It's for too little sleep.

Keith Thompson

unread,
Jan 24, 2007, 1:17:21 PM1/24/07
to
"Robert Gamble" <rgam...@gmail.com> writes:
[...]

> The description for the "d" conversion specifier says:
> "The int argument is converted to signed decimal..."
> It doesn't say "the unqualified version of the argument", would you
> conclude that the following is invalid by the same logic?
>
> #include <stdio.h>
>
> int main (void) {
> const int i = 10;
> printf("%d\n", i);
> return 0;
> }

I don't think that's an issue. As I understand it, the type of the
*object* i is "const int", but the type of the *expression* i is just
"int".

C99 6.7.3p3 says:

The properties associated with qualified types are meaningful only
for expressions that are lvalues.

In the printf call above, i is an lvalue, but it's not being used as
an lvalue.

Robert Gamble

unread,
Jan 24, 2007, 1:18:17 PM1/24/07
to

An argument is an argument both before and after promotion. Why would
you assume that the word "argument" necessarily means "argument before
promotion" as opposed to "argument after promotion"?

Robert Gamble

Robert Gamble

unread,
Jan 24, 2007, 1:31:48 PM1/24/07
to
On Jan 24, 12:34 pm, "Jun Woong" <w...@icu.ac.kr> wrote:
> "Robert Gamble" <rgambl...@gmail.com> wrote:
> > On Jan 24, 10:52 am, kuy...@wizard.net wrote:
> [...]
> > > I don't see that as being precisely the same logic.
>
> > Fine. How about this example pulled at random from the Library clause
> > of the Standard:
>
> > "C99 - 7.4 Character Handling
>
> > The header <ctype.h> declares several functions useful for classifying
> > and mapping
> > characters.168) In all cases the argument is an int, the value of which
> > shall be
> > representable as an unsigned char or shall equal the value of the macro
> > EOF."
>
> > Noting that is does not say "the argument, after promotion, is an int",
> > consider the following fragment:
>
> > char i = 1;
> > isalnum(i); /* The pre-promoted type is not int, UB? */
> > isalpha((unsigned char)i); /* Same thing, a common usage */
>
> > What say you?
> You are saying nothing but the fact that both, the descriptions for
> printf and is*, have a defect. I am afraid you logic [can't] breaks James
> Kuyper's argument.

I wasn't attempting to, just making sure I understand his position.

> #include <ctype.h>
> ...
> isprint(0.0);
>
> Do you think this results in UB or not?

Yes.

> If you do, then I don't see why
>
> isprint((char)0);
>
> does not.

Because in the former example the argument is never an int, in the
latter it is, after promotion.

> If you don't, you're just saying that most implementations of is*()
> are broken.

Robert Gamble

Wojtek Lerch

unread,
Jan 24, 2007, 2:05:29 PM1/24/07
to
"Keith Thompson" <ks...@mib.org> wrote in message
news:ln4pqgi...@nuthaus.mib.org...

> "Robert Gamble" <rgam...@gmail.com> writes:
> [...]
>> const int i = 10;
>> printf("%d\n", i);
>
> I don't think that's an issue. As I understand it, the type of the
> *object* i is "const int", but the type of the *expression* i is just
> "int".

No, the expression i is an lvalue whose type is const int. In the above
context, it undergoes an implicit conversion to an rvalue whose type is
unqualified int, and then an integer promotion that changes nothing. (If i
were declared as const short, the first conversion would change it to short
and the second to int.) Since both conversions are implicit, all those
types can be reasonably referred to as "the" type of the expression i,
depending on the context. In cases where it matters, it probably is a good
idea to be more specific and talk about the *promoted* type of the
expression, or the type of its (r)value, or something like that.

When someone refers to "the" type of an operand of an operator (such as the
function call operator), I think it's more natural to interpret that as the
type that the operand expression has after any implicit conversions. But
since the standard sometimes talks about "the" type of the argument and
sometimes about its *promoted* type, that makes it unclear whether it's
trying to make a distiction or just being inconsistent.

Wojtek Lerch

unread,
Jan 24, 2007, 2:19:03 PM1/24/07
to
"Robert Gamble" <rgam...@gmail.com> wrote in message
news:1169663507.7...@m58g2000cwm.googlegroups.com...

> On Jan 24, 12:34 pm, "Jun Woong" <w...@icu.ac.kr> wrote:
[...]

>> #include <ctype.h>
>> ...
>> isprint(0.0);
>>
>> Do you think this results in UB or not?
>
> Yes.
>
>> If you do, then I don't see why
>>
>> isprint((char)0);
>>
>> does not.
>
> Because in the former example the argument is never an int, in the
> latter it is, after promotion.

Not true. Since a prototype is in scope, the argument is implicitly
converted to the type of the parameter. The only difference between that
conversion and the integer promotion that happens when you call printf() is
how the final type is chosen (based on the parameter type in the first case,
and on the type of the argument expression in the second case).


kuy...@wizard.net

unread,
Jan 24, 2007, 2:21:21 PM1/24/07
to

Robert Gamble wrote:
> On Jan 24, 12:26 pm, kuy...@wizard.net wrote:
...

> > argument, rather than the promoted type of the argument, it technically
> > (and almost certainly unintentionally) renders the behavior of your
> > code undefined.
>
> An argument is an argument both before and after promotion. ...

Citation?

> ... Why would


> you assume that the word "argument" necessarily means "argument before
> promotion" as opposed to "argument after promotion"?

Because I think the term "argument after promotion" is meaningless;
it's no longer an argument once it has been promoted - it's a value
used to initialize the value of the corresponding parameter. Section
3.2 defines the argument of a function syntactically, as an expression
in a comma delimited list. Both default argument promotions and the
implicit conversions triggered by function prototypes are described in
the semantics section of 6.5.22. In both cases, the result is a value,
not an expression. I could find nothing in 6.5.2.2 inconsistent with
that interpretation of the terminology. There may be something
elsewhere.

Douglas A. Gwyn

unread,
Jan 24, 2007, 2:29:33 PM1/24/07
to
Robert Gamble wrote:
> The description for the "d" conversion specifier says:
> "The int argument is converted to signed decimal..."
> It doesn't say "the unqualified version of the argument", would you
> conclude that the following is invalid by the same logic?

WOrse yet, the spec for %u says "the unsigned int argument",
yet we *know* that it is intended to work for nonnegative
int values. I think too much is being read into the
statement of argument types for fprintf. Note that for
%hd there is a strong statement that the int value passed
*shall* be converted to short int before printing.

Douglas A. Gwyn

unread,
Jan 24, 2007, 2:24:30 PM1/24/07
to
Keith Thompson wrote:
> > There is no undefined behavior in the second instance. The value
> > SHRT_MAX+1, which is of type int, is converted to short; the
> > conversion either yields an implementation-defined result or raises an
> > implementation-defined signal. Assuming no signal is raised, the
> > behavior of printf() is well defined for any possible result.
> As Doug acknowledged in a followup he posted before I wrote the above,
> but I hadn't read yet.

That's okay, I was speaking from faulty memory.
Actually I still find it somewhat peculiar that the
behavior is impl.-def. but could consist of raising
an impl.-def. signal. I wonder if we did that in
any other place in the standard..
My guess is that that was in response to one of the
numerous UK issues, which had a strong theme of not
wanting u.b. in the standard.

Robert Gamble

unread,
Jan 24, 2007, 2:47:43 PM1/24/07
to

6.5.2.2p6 states in part:

"If the function is defined with a type that does not include a
prototype, and the types of
the _arguments after promotion_ are not compatible with those of the
parameters after
promotion, the behavior is undefined, except for the following cases:"

That certainly seems to imply that arguments are still arguments after
promotion but perhaps that's just another example of poor wording in
the Standard.

Robert Gamble

Robert Gamble

unread,
Jan 24, 2007, 2:51:17 PM1/24/07
to
On Jan 24, 2:19 pm, "Wojtek Lerch" <Wojte...@yahoo.ca> wrote:
> "Robert Gamble" <rgambl...@gmail.com> wrote in messagenews:1169663507.7...@m58g2000cwm.googlegroups.com...

That's not the issue, the issue is that the is* functions are
explicitly specified as receiving int arguments (reread my original
post).

Robert Gamble

Wojtek Lerch

unread,
Jan 24, 2007, 3:03:15 PM1/24/07
to
"Robert Gamble" <rgam...@gmail.com> wrote in message
news:1169668277.7...@q2g2000cwa.googlegroups.com...

> On Jan 24, 2:19 pm, "Wojtek Lerch" <Wojte...@yahoo.ca> wrote:
>> "Robert Gamble" <rgambl...@gmail.com> wrote in
>> messagenews:1169663507.7...@m58g2000cwm.googlegroups.com...
>> [...]
>> >> #include <ctype.h>

>> >> isprint(0.0);
>> >> Do you think this results in UB or not?
>> > Yes.
>>
>> >> If you do, then I don't see why
>> >> isprint((char)0);
>> >> does not.
>>
>> > Because in the former example the argument is never an int, in the
>> > latter it is, after promotion.
>>
>> Not true. Since a prototype is in scope, the argument is implicitly
>> converted to the type of the parameter. The only difference between that
>> conversion and the integer promotion that happens when you call printf()
>> is
>> how the final type is chosen (based on the parameter type in the first
>> case,
>> and on the type of the argument expression in the second case).
>
> That's not the issue, the issue is that the is* functions are
> explicitly specified as receiving int arguments (reread my original
> post).

And how is (char)0 an int? With a prototype in scope, the argument is
converted directly to the type of the parameter, and a promotion is not
involved. The converted type is int in both cases; the unconverted type is
not int in either case. Either both are defined or both undefined.

Keith Thompson

unread,
Jan 24, 2007, 4:04:00 PM1/24/07
to
"Douglas A. Gwyn" <DAG...@null.net> writes:
> Robert Gamble wrote:
> > The description for the "d" conversion specifier says:
> > "The int argument is converted to signed decimal..."
> > It doesn't say "the unqualified version of the argument", would you
> > conclude that the following is invalid by the same logic?
>
> WOrse yet, the spec for %u says "the unsigned int argument",
> yet we *know* that it is intended to work for nonnegative
> int values. I think too much is being read into the
> statement of argument types for fprintf.

Personally, I think too much was written into that statement.

> Note that for
> %hd there is a strong statement that the int value passed
> *shall* be converted to short int before printing.

--

Robert Gamble

unread,
Jan 24, 2007, 4:38:53 PM1/24/07
to
On Jan 24, 3:03 pm, "Wojtek Lerch" <Wojte...@yahoo.ca> wrote:
> "Robert Gamble" <rgambl...@gmail.com> wrote in messagenews:1169668277.7...@q2g2000cwa.googlegroups.com...

You are of course correct, I was mistakenly thinking that integer
promotions applied here.

As for whether they are defined or not:

7.4 - Character Handling states:


"The header <ctype.h> declares several functions useful for classifying
and mapping

characters.168) _In all cases the argument is an int_, the value of


which shall be
representable as an unsigned char or shall equal the value of the macro
EOF."

but 3.3 describes "argument" as an expression and 6.3.1.1 states:

"The following may be used in an expression wherever an int or unsigned
int may
be used:
- An object or expression with an integer type whose integer
conversion rank is less
than or equal to the rank of int and unsigned int."

I'm not sure if that would apply here or not.

Robert Gamble

kuy...@wizard.net

unread,
Jan 24, 2007, 8:24:38 PM1/24/07
to
Robert Gamble wrote:
> On Jan 24, 2:21 pm, kuy...@wizard.net wrote:
...

> >Because I think the term "argument after promotion" is meaningless;
> > it's no longer an argument once it has been promoted - it's a value
> > used to initialize the value of the corresponding parameter. Section
> > 3.2 defines the argument of a function syntactically, as an expression
> > in a comma delimited list. Both default argument promotions and the
> > implicit conversions triggered by function prototypes are described in
> > the semantics section of 6.5.22. In both cases, the result is a value,
> > not an expression. I could find nothing in 6.5.2.2 inconsistent with
> > that interpretation of the terminology. There may be something
> > elsewhere.
>
> 6.5.2.2p6 states in part:
>
> "If the function is defined with a type that does not include a
> prototype, and the types of
> the _arguments after promotion_ are not compatible with those of the
> parameters after
> promotion, the behavior is undefined, except for the following cases:"

I've always read that as referrting to the type of value that is
created by promoting the argument, and not as implying that it still is
an argument after it's been promoted. However, I have to admit now that
I can see your way of interpreting it as plausible.

However, even if that interpretation is correct, I would still argue
that "argument", should be take as referring to the argument before
promotion/conversion, not after, unless explicitly stated otherwise.
Otherwise, it makes all use of phrases like "the arguments after
promotion" unnecessary.

kuy...@wizard.net

unread,
Jan 24, 2007, 8:31:18 PM1/24/07
to
Robert Gamble wrote:
...

> but 3.3 describes "argument" as an expression and 6.3.1.1 states:
>
> "The following may be used in an expression wherever an int or unsigned
> int may
> be used:
> - An object or expression with an integer type whose integer
> conversion rank is less
> than or equal to the rank of int and unsigned int."

That is, I think, the single most relevant citation, and it seems to
override all of my arguments.

Keith Thompson

unread,
Jan 24, 2007, 8:57:27 PM1/24/07
to

But we still have the question of whether an int may be used as the
second argument to printf("%hd\n", whatever). If it can't, then
6.3.1.1 doesn't apply. (But common sense and committee intent both
imply that may and it does, respectively.)

Jun Woong

unread,
Jan 24, 2007, 9:10:57 PM1/24/07
to

Douglas A. Gwyn wrote:
> Robert Gamble wrote:
> > The description for the "d" conversion specifier says:
> > "The int argument is converted to signed decimal..."
> > It doesn't say "the unqualified version of the argument", would you
> > conclude that the following is invalid by the same logic?
>
> WOrse yet, the spec for %u says "the unsigned int argument",
> yet we *know* that it is intended to work for nonnegative
> int values.

If someone thinks that it is inteded to work for nonnegative int
values, then it is simply that (s)he is wrong. What you're saying
is directly against the committee answered in a C90 DR.

> I think too much is being read into the
> statement of argument types for fprintf. Note that for
> %hd there is a strong statement that the int value passed
> *shall* be converted to short int before printing.

A completely meaningless conversion unless the standard is revised to
say that every possible value that int can represent can be given for
%hd. I don't think the current standard does it.

Jun Woong

unread,
Jan 24, 2007, 10:27:44 PM1/24/07
to

"Robert Gamble" wrote:
> On Jan 24, 3:03 pm, "Wojtek Lerch" <Wojte...@yahoo.ca> wrote:

[...]


>
> As for whether they are defined or not:
>
> 7.4 - Character Handling states:
> "The header <ctype.h> declares several functions useful for classifying
> and mapping
> characters.168) _In all cases the argument is an int_, the value of
> which shall be
> representable as an unsigned char or shall equal the value of the macro
> EOF."
>
> but 3.3 describes "argument" as an expression and 6.3.1.1 states:
>
> "The following may be used in an expression wherever an int or unsigned
> int may
> be used:
> - An object or expression with an integer type whose integer
> conversion rank is less
> than or equal to the rank of int and unsigned int."
>
> I'm not sure if that would apply here or not.
>

Of course, it does not apply; see DR255.

We should take the wording in 6.3.1.1 as to mean a general
description for the integral promotion. It is not intended to
override any specific wording given elsewhere in the standard.

Jun Woong

unread,
Jan 24, 2007, 10:29:57 PM1/24/07
to

Keith Thompson wrote:
> kuy...@wizard.net writes:
> > Robert Gamble wrote:
> > ...
> > > but 3.3 describes "argument" as an expression and 6.3.1.1 states:
> > >
> > > "The following may be used in an expression wherever an int or unsigned
> > > int may
> > > be used:
> > > - An object or expression with an integer type whose integer
> > > conversion rank is less
> > > than or equal to the rank of int and unsigned int."
> >
> > That is, I think, the single most relevant citation, and it seems to
> > override all of my arguments.
>
> But we still have the question of whether an int may be used as the
> second argument to printf("%hd\n", whatever). If it can't, then
> 6.3.1.1 doesn't apply. (But common sense and committee intent both
> imply that may and it does, respectively.)
>

It is still vague what the committee intended on such a case. A
proper DR should deal with

- printf("%u\n", 1); // should be defined
- printf("%hd\n", 1); // should be defined
- printf("%hd\n", SHRT_MAX+1); (where no i-d signal possible)
// should be undefined
- printf("%p\n", (char *)pc); // should be defined
- isprint(0.0); // should be undefined
- isprint((unsigned char)0); // should be defined
- ...

for all of which literal reading of the current standard says UB.

An interesting thing is that C90 was more clear than C99 on some of
these issues.

Keith Thompson

unread,
Jan 24, 2007, 11:01:41 PM1/24/07
to
"Jun Woong" <wo...@icu.ac.kr> writes:
[...]

> It is still vague what the committee intended on such a case. A
> proper DR should deal with
>
> - printf("%u\n", 1); // should be defined

Agreed.

> - printf("%hd\n", 1); // should be defined

Agreed.

> - printf("%hd\n", SHRT_MAX+1); (where no i-d signal possible)
> // should be undefined

If printf() acts like an ordinary variadic function and, as stated,
internally converts its argument to short for "%hd", the possibilities
for this one would be:

(a) The internal conversion raises an implementation-defined signal.
(b) The internal conversion yields an implementation-defined result
of type short, which is then printed.
(c) If INT_MAX==SHRT_MAX, the addition overflows and invokes undefined
behavior.

> - printf("%p\n", (char *)pc); // should be defined

Agreed.

> - isprint(0.0); // should be undefined

Why? If isprint() is an ordinary function, it takes a single argument
of type int. As long as a correct prototype is visible, the double
value 0.0 is converted to int and passed to isprint() (and the result
must be 0, since '\0' cannot be printable).

> - isprint((unsigned char)0); // should be defined

Agreed -- but why should this be different from isprint(0.0)?

Robert Gamble

unread,
Jan 24, 2007, 11:24:13 PM1/24/07
to
On Jan 24, 10:27 pm, "Jun Woong" <w...@icu.ac.kr> wrote:
> "Robert Gamble" wrote:
> > On Jan 24, 3:03 pm, "Wojtek Lerch" <Wojte...@yahoo.ca> wrote:
> [...]
>
> > As for whether they are defined or not:
>
> > 7.4 - Character Handling states:
> > "The header <ctype.h> declares several functions useful for classifying
> > and mapping
> > characters.168) _In all cases the argument is an int_, the value of
> > which shall be
> > representable as an unsigned char or shall equal the value of the macro
> > EOF."
>
> > but 3.3 describes "argument" as an expression and 6.3.1.1 states:
>
> > "The following may be used in an expression wherever an int or unsigned
> > int may
> > be used:
> > - An object or expression with an integer type whose integer
> > conversion rank is less
> > than or equal to the rank of int and unsigned int."
>
> > I'm not sure if that would apply here or not.Of course, it does not apply; see DR255.

>
> We should take the wording in 6.3.1.1 as to mean a general
> description for the integral promotion. It is not intended to
> override any specific wording given elsewhere in the standard.

That's what I'm not sure about and I am not yet convinced either way.
I don't see how DR 255 clarifies any of this either.

Robert Gamble

Robert Gamble

unread,
Jan 24, 2007, 11:53:30 PM1/24/07
to
On Jan 24, 11:01 pm, Keith Thompson <k...@mib.org> wrote:
> "Jun Woong" <w...@icu.ac.kr> writes:[...]

>
> > It is still vague what the committee intended on such a case. A
> > proper DR should deal with
>
> > - printf("%u\n", 1); // should be definedAgreed.
>
> > - printf("%hd\n", 1); // should be definedAgreed.
>
> > - printf("%hd\n", SHRT_MAX+1); (where no i-d signal possible)
> > // should be undefinedIf printf() acts like an ordinary variadic function and, as stated,

> internally converts its argument to short for "%hd", the possibilities
> for this one would be:
>
> (a) The internal conversion raises an implementation-defined signal.
> (b) The internal conversion yields an implementation-defined result
> of type short, which is then printed.
> (c) If INT_MAX==SHRT_MAX, the addition overflows and invokes undefined
> behavior.
>
> > - printf("%p\n", (char *)pc); // should be definedAgreed.
>
> > - isprint(0.0); // should be undefined
>
>Why?

Because subclause 7.4 states that the *argument* is an int, I have
cited the appropriate wording a couple of times already in this thread.

Robert Gamble

Douglas A. Gwyn

unread,
Jan 25, 2007, 12:08:33 AM1/25/07
to
"Jun Woong" <wo...@icu.ac.kr> wrote...

> Douglas A. Gwyn wrote:
>> Note that for
>> %hd there is a strong statement that the int value passed
>> *shall* be converted to short int before printing.
> A completely meaningless conversion unless the standard is revised to
> say that every possible value that int can represent can be given for
> %hd. I don't think the current standard does it.

Actually it is a useless operation if the argument can only be those
values representable in type short int.


Jun Woong

unread,
Jan 25, 2007, 12:27:01 AM1/25/07
to

Keith Thompson wrote:


> "Jun Woong" <w...@icu.ac.kr> writes:
> [...]
>
> > It is still vague what the committee intended on such a case. A
> > proper DR should deal with
> >
> > - printf("%u\n", 1); // should be definedAgreed.
> >
> > - printf("%hd\n", 1); // should be definedAgreed.
> >
> > - printf("%hd\n", SHRT_MAX+1); (where no i-d signal possible)
> > // should be undefined
>
> If printf() acts like an ordinary variadic function and, as stated,
> internally converts its argument to short for "%hd", the possibilities
> for this one would be:
>
> (a) The internal conversion raises an implementation-defined signal.
> (b) The internal conversion yields an implementation-defined result
> of type short, which is then printed.
> (c) If INT_MAX==SHRT_MAX, the addition overflows and invokes undefined
> behavior.
>

As said, simply rendering it UB would embrace most of the existing
implementations. Besides, it is not asymmetric with the behavior of
*scanf. Personally I don't care which one is chosen.

[...]


> >
> > - isprint(0.0); // should be undefined
>
> Why? If isprint() is an ordinary function, it takes a single argument
> of type int. As long as a correct prototype is visible, the double
> value 0.0 is converted to int and passed to isprint()

It is very common for an implementation to mask is* functions with
macros that take the argument as an index to an internal table, which
means that the argument should be of an integer type. An
implementation which do that should do either

#define isprint(x) (__table[(x)] & __P)

or

#define isprint(x) (__table[(__int)(x)] & __P)

The former can't accept 0.0 with emitting a correct diagnostic for

isprint((char *)some_ptr);

while the latter is very likely not to emit it with accepting 0.0.

Rendering isprint(0.0) would help to implement masking macros for
is* functions.

> (and the result
> must be 0, since '\0' cannot be printable).
>

In most implementations.

Keith Thompson

unread,
Jan 25, 2007, 1:30:55 AM1/25/07
to
"Jun Woong" <wo...@icu.ac.kr> writes:

> Keith Thompson wrote:
[...]
> > >
> > > - isprint(0.0); // should be undefined
> >
> > Why? If isprint() is an ordinary function, it takes a single argument
> > of type int. As long as a correct prototype is visible, the double
> > value 0.0 is converted to int and passed to isprint()
>
> It is very common for an implementation to mask is* functions with
> macros that take the argument as an index to an internal table, which
> means that the argument should be of an integer type. An
> implementation which do that should do either
>
> #define isprint(x) (__table[(x)] & __P)
>
> or
>
> #define isprint(x) (__table[(__int)(x)] & __P)
>
> The former can't accept 0.0 with emitting a correct diagnostic for
>
> isprint((char *)some_ptr);
>
> while the latter is very likely not to emit it with accepting 0.0.
>
> Rendering isprint(0.0) would help to implement masking macros for
> is* functions.

There are plenty of functions in the standard library that take
integer arguments, and any of them can be implemented as a macro. So
the restriction to arguments of type int is applied only to the is*()
and to*() functions, specifically to enable a specific class of macro
implementations. In fact, the Rationale says so:

Since these functions are often used primarily as macros, their
domain is restricted to the small positive integers representable
in an unsigned char, plus the value of EOF. EOF is traditionally
-1, but may be any negative integer, and hence distinguishable
from any valid character code. These macros may thus be
efficiently implemented by using the argument as an index into a
small array of attributes.

But I'm still not 100% clear on all the corner cases. The standard says:

In all cases the argument is an int, the value of which shall be


representable as an unsigned char or shall equal the value of the

macro EOF. If the argument has any other value, the behavior is
undefined.

But what if it has any other *type*? Requiring an argument to be of a
specific type, forbidding (well, specifying undefined behavior for)
cases that would be well defined for an ordinary function, is unusual
enough that it should be spelled out explicitly.

Does isprint((char)'a') invoke UB?

> > (and the result
> > must be 0, since '\0' cannot be printable).
> >
>
> In most implementations.

Again, I think you're right. The term "printing character" is defined
only in 7.4 (<ctype.h>), not in 5.2.1 (Character sets), and the
definition there doesn't exclude the possibility of '\0' being a
printing character. But making it one would be extremely
inconvenient, and I doubt that any existing system (with a conforming
C implementation) does so.

Keith Thompson

unread,
Jan 25, 2007, 1:32:52 AM1/25/07
to
"Robert Gamble" <rgam...@gmail.com> writes:
> On Jan 24, 11:01 pm, Keith Thompson <k...@mib.org> wrote:
> > "Jun Woong" <w...@icu.ac.kr> writes:[...]
[...]

> > > - isprint(0.0); // should be undefined
> >
> >Why?
>
> Because subclause 7.4 states that the *argument* is an int, I have
> cited the appropriate wording a couple of times already in this thread.

My thinking was that the wording in 7.4 isn't necessarily correct, and
a DR could fix it. But Jun Woong pointed out, and I agree, that the
functions in <ctype.h> are a special case; see my other recent
response in this thread.

Jun Woong

unread,
Jan 25, 2007, 1:43:17 AM1/25/07
to

"Robert Gamble" wrote:
> On Jan 24, 10:27 pm, "Jun Woong" <w...@icu.ac.kr> wrote:
[...]

> > > I'm not sure if that would apply here or not.
>
> > Of course, it does not apply; see DR255.
> >
> > We should take the wording in 6.3.1.1 as to mean a general
> > description for the integral promotion. It is not intended to
> > override any specific wording given elsewhere in the standard.
>
> That's what I'm not sure about and I am not yet convinced either way.
> I don't see how DR 255 clarifies any of this either.
>

Oh, sorry. I mixed up it with int vs. unsigned issue.
Now I agree with you on this. That seems to save

isprint((char)0);

from being undefined behavior, even if, as Keith pointed out, it does
not give an answer to the printf("%hd", 0) problem.

Harald van Dijk

unread,
Jan 25, 2007, 1:56:40 AM1/25/07
to
Jun Woong wrote:
> Keith Thompson wrote:
> > "Jun Woong" <w...@icu.ac.kr> writes:
> [...]
> > >
> > > - isprint(0.0); // should be undefined
> >
> > Why? If isprint() is an ordinary function, it takes a single argument
> > of type int. As long as a correct prototype is visible, the double
> > value 0.0 is converted to int and passed to isprint()
>
> It is very common for an implementation to mask is* functions with
> macros that take the argument as an index to an internal table, which
> means that the argument should be of an integer type. An
> implementation which do that should do either
>
> #define isprint(x) (__table[(x)] & __P)
>
> or
>
> #define isprint(x) (__table[(__int)(x)] & __P)
>
> The former can't accept 0.0 with emitting a correct diagnostic for
>
> isprint((char *)some_ptr);
>
> while the latter is very likely not to emit it with accepting 0.0.
>
> Rendering isprint(0.0) would help to implement masking macros for
> is* functions.

A masking macro for isprint can be implemented without any new
permissions from the standard.

#define isprint(x) (__table[(__int){x}] & __P)

kuy...@wizard.net

unread,
Jan 25, 2007, 6:44:23 AM1/25/07
to
> override all of my arguments.\

I was so tired, and in particular so tired of this discussion, that I
didn't pay attention to the fact that this citation covers the reverse
of the situation that we've been discussing. 6.3.1.1 says that
printf("%d\n", (short)42) is legal; it doesn't say anything which
applies to printf("%hd\n", 42).

lawrenc...@ugs.com

unread,
Jan 25, 2007, 4:03:50 PM1/25/07
to
Keith Thompson <ks...@mib.org> wrote:
>
> Paragraph 7 seems to imply that the "correct type" for "%hd" is short,
> so passing an argument of type int would invoke undefined behavior.
> But a short argument is going to be promoted to int anyway.

Exactly, which is why the standard uses the imprecise English term
"correct" rather than the precise Standardeese term "compatible".
(Perhaps it would have been clearer to say "a" correct type rather than
"the" correct type. Or maybe not.) The intent was that anything that's
valid for varargs in general is valid for printf and friends.

-Larry Jones

No one can prove I did that!! -- Calvin

lawrenc...@ugs.com

unread,
Jan 25, 2007, 4:09:28 PM1/25/07
to
Jun Woong <wo...@icu.ac.kr> wrote:
>
> Or what about this? I'm not talking about an imaginary implementation,
> but a real practical implementation. If one takes the wording in
> question as to mean "take an int that is the promoted type of short
> and convert it to short before printing

That is precisely what it means -- that's why it uses the magic word
"shall". So, if you pass -1 to %hu, you should get USHRT_MAX printed,
not UINT_MAX or some other value (or undefined behavior).

-Larry Jones

You don't get to be Mom if you can't fix everything just right. -- Calvin

lawrenc...@ugs.com

unread,
Jan 25, 2007, 4:12:00 PM1/25/07
to
Douglas A. Gwyn <DAG...@null.net> wrote:
>
> That's okay, I was speaking from faulty memory.
> Actually I still find it somewhat peculiar that the
> behavior is impl.-def. but could consist of raising
> an impl.-def. signal. I wonder if we did that in
> any other place in the standard..

No.

> My guess is that that was in response to one of the
> numerous UK issues, which had a strong theme of not
> wanting u.b. in the standard.

It was more specifically a general desire to allow trapping signed
integer overflow to promote reliability. Quiet wraparound can produce
completely erroneous results without any indication that anything went
wrong.

-Larry Jones

It's clear I'll never have a career in sports until I learn
to suppress my survival instinct. -- Calvin

Jun Woong

unread,
Jan 25, 2007, 9:19:57 PM1/25/07
to

Harald van Dijk wrote:
> Jun Woong wrote:
[...]

> >
> > Rendering isprint(0.0) would help to implement masking macros for
> > is* functions.
>
> A masking macro for isprint can be implemented without any new
> permissions from the standard.

It's not new. It's been there since C90. What this discussion says
is about clarifying it.

>
> #define isprint(x) (__table[(__int){x}] & __P)

I didn't think about this much, but it seems to work well except
that on some implementations the issued diagnostic message can make
its readers confused; it doesn't matter to the standard, but some
committee members may be against it for that reason. Provided that
no technical problem, I think this really can encourage the
committee to get rid of the unique permission from the standard.

Jun Woong

unread,
Jan 25, 2007, 9:24:22 PM1/25/07
to

lawrence.jo...@ugs.com wrote:
> [...] The intent was that anything that's

> valid for varargs in general is valid for printf and friends.
>

Which should have been given explicitly in the text of the standard.
Literal reading of the standard can say a completely different thing
from the intent.

Jun Woong

unread,
Jan 25, 2007, 9:32:51 PM1/25/07
to

lawrence.jo...@ugs.com wrote:

> Jun Woong <w...@icu.ac.kr> wrote:
> >
> > Or what about this? I'm not talking about an imaginary implementation,
> > but a real practical implementation. If one takes the wording in
> > question as to mean "take an int that is the promoted type of short
> > and convert it to short before printing
>
> That is precisely what it means -- that's why it uses the magic word
> "shall". So, if you pass -1 to %hu, you should get USHRT_MAX printed,
> not UINT_MAX or some other value (or undefined behavior).
>

Huh? For %hd, I think it is quite reasonable to restrict the possible
range of values which doesn't invoke UB to [SHRT_MIN, SHRT_MAX], and
don't see any reason not to do the same thing for %hu. The correct
type of an argument for %hu is unsigned short. Thus, the allowed
range of values should be [0, USHRT_MAX]. Do you think the following
is a s.c. code and should print UINT_MAX?

printf("%u", -1);

Keith Thompson

unread,
Jan 25, 2007, 10:52:42 PM1/25/07
to
≈慲慬搠癡渠䓾!㍫∠㱴牵敤晸䁧浡楬⹣潭㸠睲楴敳㨊嬮⸮崊㸠䄠浡獫楮朠浡捲漠景爠楳灲楮琠捡渠扥⁩浰汥浥湴敤⁷楴桯畴⁡湹敷ਾ⁰敲浩獳楯湳⁦牯洠瑨攠獴慮摡牤⸊㸠ਾ‣摥晩湥⁩獰物湴⡸⤠⡟彴慢汥嬨彟楮琩筸絝… 彐⤊੗慳⁻硽敡湴⁴漠扥
砩㼊੗桡琠楳 彩湴⁳異灯獥搠瑯⁢政ਊⴭ 䭥楴栠周潭灳潮
周敟佴桥牟䭥楴栩獴⵵䁭楢⹯牧†㱨瑴瀺⼯睷眮杨潴椮湥琯繫獴㸊卡渠䑩敧漠卵灥牣潭灵瑥爠䍥湴敲††††††‼⨾†㱨瑴瀺⼯畳敲献獤獣⹥摵⽾歳琾੗攠浵獴⁤漠獯浥瑨楮朮†周楳⁩猠獯浥瑨楮朮†周敲敦潲攬⁷攠浵獴⁤漠瑨楳⸊

Jun Woong

unread,
Jan 25, 2007, 11:17:48 PM1/25/07
to

Keith Thompson wrote:
> "Harald van Dþÿ 3k" <true...@gmail.com> writes:
> [...]

>
> > A masking macro for isprint can be implemented without any new
> > permissions from the standard.
>
> > #define isprint(x) (__table[(__int){x}] & __P)
>
> Was {x} meant to be (x)?
>

No. He meant a compound literal. That enables an appropriate
diagnostic to be issued with still converting 0.0 to 0.

> What is __int supposed to be?
>

I'm not sure I got the point of your questions, but a s.c. program
is allowed to do this:

#include <ctype.h>

#define int double

...

isprint((unsigned char)c);

Jun Woong

unread,
Jan 26, 2007, 12:22:58 AM1/26/07
to
[Google's recently upgraded usenet service is so unstable that it
consumed my post without posting it. I rewrite it based on my memory
but I apologize if you see redundant posts with the same point but
in different words.]

lawrence.jo...@ugs.com wrote:
[...]


> > That is precisely what it means -- that's why it uses the magic word
> > "shall". So, if you pass -1 to %hu, you should get USHRT_MAX printed,
> > not UINT_MAX or some other value (or undefined behavior).
>

I wrote:
> Huh? For %hd, I think it is quite reasonable to restrict the possible
> range of values which doesn't invoke UB to [SHRT_MIN, SHRT_MAX], and
> don't see any reason not to do the same thing for %hu. The correct
> type of an argument for %hu is unsigned short. Thus, the allowed
> range of values should be [0, USHRT_MAX]. Do you think the following
> is a s.c. code and should print UINT_MAX?
>
> printf("%u", -1);
>

I shouldn't have put the last sentence with an example that is
inappropriate to show my point; Ignore it please.

As said before, there are three possible behaviors that one can think
%hd is intended to have. What you're saying here is also one of them:

The type allowed for an argument corresponding to %hd and %hu is in
fact int or unsigned int, and the value should lie down in
[INT_MIN, INT_MAX] or [0, UINT_MAX] respectively, which is converted
to a proper type in *printf before printing.

However, unfortunately what the standard specifies is far from this.
This is because it mentions "short" and "u-short" rather than "int"
or "u-int" as an allowed type. It implies at most that an argument
of type int or u-int is allowed but its value has to be in the
representable range of short or u-short. The fact that we've had an
widespread implementation that simply ignores 'h' in %hd also
strongly supports that the standard seriously failed to deliver the
intended interpretation if what you said was the intent.

Without revising the text (especially saying "short" and "unsigned
short" as the type of an argument), it seems very unreasonable to
claim that what you said is what the current wording of the
standard delivers; Rather, it makes more sense to read it as to mean
the literal (most strict) interpretation or at least allowing int or
u-int but with still limiting the value into the range short or
u-short can represent.

Should an implemention ignoring 'h' in %hd or %hu be revised? I hope
to see the answer in a DR, not here.

Keith Thompson

unread,
Jan 26, 2007, 2:18:54 AM1/26/07
to
"Jun Woong" <wo...@icu.ac.kr> writes:
> Keith Thompson wrote:
> > "Harald van Dþÿ 3k" <true...@gmail.com> writes:
> > [...]
> >
> > > A masking macro for isprint can be implemented without any new
> > > permissions from the standard.
> >
> > > #define isprint(x) (__table[(__int){x}] & __P)
> >
> > Was {x} meant to be (x)?
>
> No. He meant a compound literal. That enables an appropriate
> diagnostic to be issued with still converting 0.0 to 0.

Ok. I hadn't realized compound literals could be used for scalar
types.

> > What is __int supposed to be?
> >
>
> I'm not sure I got the point of your questions, but a s.c. program
> is allowed to do this:
>
> #include <ctype.h>
>
> #define int double
>
> ...
>
> isprint((unsigned char)c);

The point was that I didn't think of that. Thanks.

Harald van Dijk

unread,
Jan 26, 2007, 2:22:53 AM1/26/07
to
Jun Woong wrote:
> Harald van Dijk wrote:
> > Jun Woong wrote:
> [...]
> > >
> > > Rendering isprint(0.0) would help to implement masking macros for
> > > is* functions.
> >
> > A masking macro for isprint can be implemented without any new
> > permissions from the standard.
>
> It's not new. It's been there since C90. What this discussion says
> is about clarifying it.

That's your viewpoint. Mine is that it's new, it's not been there since
C90, and what your posts in this discussion say is about adding that
permission to match existing practice. 7.4p1 does not say the argument
"shall be" of type int, it says it "is", so I believe this is meant as
nothing more than a slightly poorly worded description of the functions
that follow, not a requirement on programs.

I can find an argument that makes

#include <stdio.h>
int main(void) { puts("Hello, world!"); }

undefined behaviour, based on poor wording in the standard. I think the
intent is that the program is strictly conforming, and that in this
case I'll rely on the intent. :)

/If/ the standard actually does already say and is meant to say that
isprint(0.0) is undefined, though, this is "new" at least to some in
this thread (myself included) in the sense of "newly discovered".

Douglas A. Gwyn

unread,
Jan 26, 2007, 2:16:00 AM1/26/07