Strange behaviour

O

Old Wolf

pete said:
C99
6.3.1 Arithmetic operands
6.3.1.1 Boolean, characters, and integers

2 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
the rank of int and unsigned int.

OK; so passing the signed char to %X is well-defined
as well then. Is it guaranteed to produce the same
result as casting the signed char to unsigned int first?
 
D

Dik T. Winter

>
> It looks that way. Section 7 says:
> hh Specifies that a following d, i, o, u, x, or X conversion
> specifier applies to a signed char or unsigned char
> argument

You conveniently snipped: "(the argument will have been promoted according
to the integer promotions, but its value shall be converted to
signed char or unsigned char before printing)". So here the standard is
clearly talking about the type before promotion.
> By your reasoning these modifiers could never be used, as
> the argument is always an int (assuming we are talking
> about systems where chars and shorts promote to int).

No, see what you snipped.
> I think that it is inconsistent to read "argument" as the pre-
> promotion argument in some clauses, and to read it as the
> post-promotion argument in other clauses of the same
> section.

It is clearly identified here that the talk is about the type before
promotion.
> In fact, those section 7 entries have a suffix clarifying that
> they are talking about the pre-promotion argument. Perhaps
> a DR is in order for section 8 to clarify that your code
> should not cause UB.

I think that that is not needed. At least it is clear to me that there
is no UB.
>
> In your above code, the argument is 'a' of type char. After the
> default argument promotions, the argument is 'a' of type int.
> Right?

No. 'a' is of type int. c is of type char. c contains the value
of 'a' converted to type char. After the promotion it is of type
int, and hopefully identical to the original. (The latter is not
guaranteed. For instance, if char is signed char, the following:
char c = '\240';
printf("%X %X\n", c, '\240');
can produce differing output.)

However, there is something else here. X requires that the argument
is of type unsigned int. But the arguments supplied are of type int.
I think there is definitely a problem if c is of type char (with
char being signed) and also negative.
 
D

Dik T. Winter

>
> OK; so passing the signed char to %X is well-defined
> as well then. Is it guaranteed to produce the same
> result as casting the signed char to unsigned int first?

No, it is not well-defined. Passing a signed char passes an int.
%X expects an unsigned int, and at that stage no conversion takes
place. See 6.5.2.2#6, where there is talk about compatible types
in function calls, if the two are not compatible it is still
well-defined if one type is signed integer, the other unsigned
integer and the value representable for both. In the case of %X
this means that if a signed char is passed, it is only well-defined
if the value is >= 0. So in:
signed char c = '\240';
printf("%X %X\n", c, '\240');
the conversion of the first is undefined, the conversion of the
second is well-defined (assuming 8-bit chars).
 
O

Old Wolf

Dik said:
No, it is not well-defined. Passing a signed char passes an int.
%X expects an unsigned int, and at that stage no conversion takes
place. See 6.5.2.2#6, where there is talk about compatible types
in function calls, if the two are not compatible it is still
well-defined if one type is signed integer, the other unsigned
integer and the value representable for both. In the case of %X
this means that if a signed char is passed, it is only well-defined
if the value is >= 0.

Ok, that's what 6.5.2.2#6 says. At first this appears to
conflict with 6.3.1.1 which says that a signed char can be
used wherever an unsigned int can be used.

I was reading 6.3.1.1's "may be used" as "may be used,
with well-defined behaviour". But I think this is not correct:
although it says a signed char may be used, other constraints
may still apply that render it undefined.

For example:

char ch = -2;
isalpha(ch);

? isalpha's argument is a place where an int or unsigned int
may be used, but this causes undefined behaviour because
it violates the requirement that the argument to isalpha be
in the range 0 ... UCHAR_MAX.

If you agree so far, then it follows that 6.3.1.1 actually
doesn't have any bearing on the issue we were discussing.
My original position was that the rules in 7.19.6 (fprintf)
imply that passing the char to %X caused UB, and we
have just established that other rules can 'trump' 6.3.1.1.

If I can continue replying to your other post here instead of
having 2 sub-threads: it looks like the resolution is to say
that 7.19.6#7 is talking about the pre-promotion argument,
but #8 ("The int parameter to %d...") is talking about the
post-promotion argument. This is clearly the intent,
although it was not clear to me at first that the wording
expressed this intent accurately.

Have we resolved the case of:
char c = 0x20;
printf("%X\n", c);
?

6.5.2.2#6 says:
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:
-- one promoted type is a signed integer type, the other
promoted type is the corresponding unsigned integer type,
and the value is representable in both types;

-- both types are pointers to qualified or unqualified versions
of a character type orvoid.

But the fprintf call is not a function defined with a type that does
not include a prototype. Even if we ignore that clause, and
assume that everything mentioned in 6.5.2.2#6 is part of
"default argument promotions", then how can you compare
the post-promotion argument type with the "type of the
parameter" ? The parameter is "..." which has no type.

If we assume that the definition of fprintf also provides
definitions of parameters to which 6.5.2.2#6 can be applied,
that would solve the problem.

A corollary of this would be that you can pass (char *)
and (unsigned char *) pointers as parameters to %p,
without having to cast them to (void *) first.
 
D

Dik T. Winter

> Dik T. Winter wrote: ....
>
> Ok, that's what 6.5.2.2#6 says. At first this appears to
> conflict with 6.3.1.1 which says that a signed char can be
> used wherever an unsigned int can be used.

But that is about operands in expression, so it does not apply here.
> ? isalpha's argument is a place where an int or unsigned int
> may be used, but this causes undefined behaviour because
> it violates the requirement that the argument to isalpha be
> in the range 0 ... UCHAR_MAX.

But range violations are again something different.
> My original position was that the rules in 7.19.6 (fprintf)
> imply that passing the char to %X caused UB, and we
> have just established that other rules can 'trump' 6.3.1.1.

Yup. I think that if 'char' is signed that it may result in UB, but
when it is unsigned that it does not.

....
> Have we resolved the case of:
> char c = 0x20;
> printf("%X\n", c);
> ?
>
> 6.5.2.2#6 says:
> 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:
> -- one promoted type is a signed integer type, the other
> promoted type is the corresponding unsigned integer type,
> and the value is representable in both types;
>
> -- both types are pointers to qualified or unqualified versions
> of a character type orvoid.
>
> But the fprintf call is not a function defined with a type that does
> not include a prototype.

Yes, I think 6.5.2.2#6 is sloppy. It does not say anything about compatible
types in calls to variadic functions (where the expected type is also not
known to the compiler). I think the compatibility rules are intended to
also apply in this case, but that is not mentioned. The intent is that
if in the representation (and size) of a specific value the two types are
not different, they are compatible.
> If we assume that the definition of fprintf also provides
> definitions of parameters to which 6.5.2.2#6 can be applied,
> that would solve the problem.

6.5.2.2#6 should be augmented so that the clause also applies to parameters
of variadic functions and the type that is expected at that place.
 
D

Dik T. Winter

Time for a cross-post to comp.std.c. For those in comp.std.c, the
following came up, is this program valid or does it show undefined
behaviour?

/* provides a prototype for printf */
#include <stdio.h>

int main(void) {
char c = 'a';

printf("%X\n", c);
return 0;
}

According to 6.3.1.1, in the call 'c' is promoted to int (under assumptions
that are commonly satisfied). According to 7.19.6.1#8, %X expects an
unsigned int. And there is nothing in either 6.5.2.2#6 or 6.5.2.2#7 that
tells me the two are compatible in this case (6.5.2.2#6 talks only about
compatibility if there is no prototype, but there is one). So according
to 7.19.6.1 the behaviour is undefined. My conclusion was:
> 6.5.2.2#6 should be augmented so that the clause also applies to parameters
> of variadic functions and the type that is expected at that place.

Where is my reasoning wrong?
 
R

Robert Gamble

Dik said:
Time for a cross-post to comp.std.c. For those in comp.std.c, the
following came up, is this program valid or does it show undefined
behaviour?

/* provides a prototype for printf */
#include <stdio.h>

int main(void) {
char c = 'a';

printf("%X\n", c);
return 0;
}

According to 6.3.1.1, in the call 'c' is promoted to int (under assumptions
that are commonly satisfied). According to 7.19.6.1#8, %X expects an
unsigned int. And there is nothing in either 6.5.2.2#6 or 6.5.2.2#7 that
tells me the two are compatible in this case (6.5.2.2#6 talks only about
compatibility if there is no prototype, but there is one). So according
to 7.19.6.1 the behaviour is undefined. My conclusion was:


Where is my reasoning wrong?

In the above example c is guaranteed to be positive. Since the
representation of a valid positive int value must also be a valid
representation for the same value for an unsigned int, the behavior is
well-defined. If c was negative, the behavior would be undefined.

Robert Gamble
 
F

Francis Glassborow

In the above example c is guaranteed to be positive. Since the
representation of a valid positive int value must also be a valid
representation for the same value for an unsigned int, the behavior is
well-defined. If c was negative, the behavior would be undefined.

Robert Gamble
And, I think that is unreasonable even though it seems to be a correct
reading of the Standard. The requirements of the relationship between
unsigned and signed integer types results in essentially requiring that
a signed and unsigned integer type of the same rank will have identical
bit patterns with the exception of one bit that is used as a sign bit in
the signed version.

Of course what the signed bit pattern represents as an unsigned value
will depend on which binary representation is used for negative values
and possibly for bizarre representations which bit is the sign bit but I
think that is as far as it goes.

Is there any conforming way in which a signed integer value can be a
trap value for an unsigned but otherwise equivalent integer type? If not
we should, IMO, remove this undefined behaviour from the standard.
 
D

Dik T. Winter

>
> In the above example c is guaranteed to be positive. Since the
> representation of a valid positive int value must also be a valid
> representation for the same value for an unsigned int, the behavior is
> well-defined. If c was negative, the behavior would be undefined.

But where do you find it is well-defined? According to 7.19.6.1 it is
undefined (the two types int and unsigned int do not match).
 
W

Wojtek Lerch

Dik T. Winter said:
But where do you find it is well-defined? According to 7.19.6.1 it is
undefined (the two types int and unsigned int do not match).

You won't find it in any normative text that directly applies to printf(),
but a footnote explains that that was the intent. Also, it has been argued
here that the promises that the standard makes about va_arg() automatically
apply to printf(), but not everybody was convinced.

http://groups.google.com/group/comp...ead/19dfa8e3701243df/60f890bd34eae947?rnum=57
 
J

Jean-Marc Bourguet

Francis Glassborow said:
Is there any conforming way in which a signed integer value can be a trap
value for an unsigned but otherwise equivalent integer type? If not we
should, IMO, remove this undefined behaviour from the standard.

A possibility is that the padding bits are kept to 0 for unsigned
types, when they follow the sign for unsigned types. It would be a
way to implement unsigned type when the machine has only a signed one
(AFAIR, Knuth's MIX would be one such machine; I wonder if it has ever
be the case for true binary machine, that seem to me more a
characteristic of decimal machine and MIX is defined in a way that it
could be either binary or decimal).

Yours,
 
D

Dave Thompson

Dik T. Winter wrote:
Ok, that's what 6.5.2.2#6 says. At first this appears to
conflict with 6.3.1.1 which says that a signed char can be
used wherever an unsigned int can be used.
6.5.2.2 doesn't apply here; it's about nonprototyped = oldstyle calls.
All variadic routines including printf must be declared prototyped.
The (not coincidentally) identical semantics are specified for
_user-written_ variadic arguments in 7.15.1.1. There isn't any
explicit statement that the variadic standard-library routines *printf
and *scanf must actually use va_arg et al, and indeed the standard
clearly allows that the standard library routines aren't written in C
at all, but it seems to have been intended that they are "as-if" in C
and particularly "as-if" using va_arg, and the more specific rules in
7.19.6 and 7.24.2 are visibly consistent with this.
I was reading 6.3.1.1's "may be used" as "may be used,
with well-defined behaviour". But I think this is not correct:
although it says a signed char may be used, other constraints
may still apply that render it undefined.
I think the confusion is that all of 6.3 is about conversions. "may be
used" should be read as "can and will be converted implicitly (without
a cast) if and when conversions are done".

If I can continue replying to your other post here instead of
having 2 sub-threads: it looks like the resolution is to say
that 7.19.6#7 is talking about the pre-promotion argument,
but #8 ("The int parameter to %d...") is talking about the
post-promotion argument. This is clearly the intent,
although it was not clear to me at first that the wording
expressed this intent accurately.
You mean 7.19.6.1 for fprintf. p7 is really talking about both, for
the value cases (i.e. not %n); e.g. h expects [unsigned] short
argument "[which] will have been promoted ... but shall be converted".
Clearly this means the argument is actually passed as a signed or
unsigned int according to the rules in 6.3.1.1, but is presumed to
have been promoted from a short value and is converted back to that
short value for formatting.

But the fprintf call is not a function defined with a type that does
not include a prototype. Even if we ignore that clause, and

Actually we don't know that. The _definition_ is inside the library
and up to the implementation. It is quite likely to be written in C
and use a prototype with an ellipsis and va_arg et al, but this is not
required. What we do know is that it is called through a declaration,
that must be a prototype said:
assume that everything mentioned in 6.5.2.2#6 is part of
"default argument promotions", then how can you compare

The first sentence are the default argument promotions. The two items
at the end of the paragraph are exceptions to the compatability rules,
and the same two items are exceptions in 7.15.1.1.
the post-promotion argument type with the "type of the
parameter" ? The parameter is "..." which has no type.
Right. For varargs, at least user-written varargs, in 7.15.1.1 what
must match modulo the exceptions is the promoted type of the actual
argument and the 'expected' type used to extract it.
If we assume that the definition of fprintf also provides
definitions of parameters to which 6.5.2.2#6 can be applied,
that would solve the problem.

A corollary of this would be that you can pass (char *)
and (unsigned char *) pointers as parameters to %p,
without having to cast them to (void *) first.

/*plain*/ char*, signed char*, and unsigned char*, which are (all)
formally incompatible, but all within the exception in 7.15.1.1 --
assuming it applies -- yes. Although personally I would still cast
just to be clear and consistent and robust against maintenance.

- David.Thompson1 at worldnet.att.net
 

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,768
Messages
2,569,575
Members
45,053
Latest member
billing-software

Latest Threads

Top