🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: comp.std.c
Article #3525

Re: Integral promotion.

#3525
From: Chris Torek
Date: Thu, 15 Feb 1996 03:08
175 lines
7556 bytes
(I sent this as a reply by mail but am also posting it.)

In article <4fstj7$2l6@solutions.solon.com> you write:
>I got a problem understanding the rules on integral promotion in 
>the C language ...

The rules in C are a little over-complicated (they were once entirely
straightforward) but the complications mostly involve `unsigned'.

It seems a little odd that you mention `Visual C++ 4.0', which
sounds like a C++ compiler; C++ is an entirely different language
with entirely different subtleties.  (For instance, (sizeof 'x')
is always 1 in C++ but is typically 2 or 4 in C.)

>Consider the following function:
>
>short test(short x1, short x2);

The prototype is important.

We can mostly ignore the (valid) main() you provided...

>short test(short x1, short x2)
>{
>  short result;
>  result = x1 + x2;  /* Warning:  '=' : conversion from 'int '
>                         to 'short ', possible loss of data */
>  return result;
>}

Again, we have a prototype-style definition, so the declaration
and definition match.

`Old' (pre-ANSI) C always performed `the integral promotions' on
all parameters.  For this reason, ANSI/ISO C does the same with
parameters for which a prototype is not available -- this covers
both `functions without prototypes' and `functions whose prototype
ends in ", ..."', e.g., printf().  (This means that, for instance,
you can never pass a short to printf().  By the time printf()
receives it, the value has been converted to an int.)  In Old C,
if you defined a function like this:

	void func(i, s, c)
		int i;
		short s;
		char c;
	{
	}

the compiler would take the three `int's that it knew had been
passed (due to the integral promotions), put the first in `i',
narrow the second to `short' and put it in `s', and narrow the
third and put it in `c'.  This promotion and narrowing sometimes
needs extra instructions (it depends on the machine, and maybe
the type as well).

For efficiency and/or less-surprising behavior, ANSI C says that
prototyped parameters (as you have here) are not promoted.  (Many
compilers simply promote them at the caller, and then narrow them
before entering the function, just as for Old C, but they can now
avoid these steps in this case.)

Because ANSI C requires promotion for non-prototypes and non-promotion
for prototypes, it is important to make sure you never mix up the
two kinds of declarations and definitions when you use `narrow'
types (char, short, signed char, unsigned char, unsigned short,
and float).

Anyway, with all that out of the way, we have a correct declaration
and definition for function test(), with `x1', `x2', and `result' all
being `short'.

Now, the last hard part is the line:

	result = x1 + x2;

This takes the values in x1 and x2 (both of type (signed) short
and hence in the range [SHRT_MIN .. SHRT_MAX]), performs the
integral promotions (yeilding values of type (signed) int but still
in the range [SHRT_MIN .. SHRT_MAX]), and adds them.  At this
point, the sum still has type (signed) int and could potentially
be in the range [2*SHRT_MIN .. 2*SHRT_MAX].

Now, if `int' and `short' are the same width, a result that would
be outside the range [SHRT_MIN .. SHRT_MAX] is also outside the
range [INT_MIN .. INT_MAX], and the behavior is undefined (ANSI C
section 3.3, p. 39, ll. 15--17).

On the other hand, if `int's are wider than `short's, we are pretty
safe (maybe not completely safe, but pretty safe anyway) in assuming
that INT_MIN <= 2*SHRT_MIN and INT_MAX >= 2*SHRT_MAX.  Most likely
INT_MIN and INT_MAX are something like 65536 times bigger.  Either
way, the sum is not outside the representable range and the undefined
clause in 3.3 does not apply.

The final step is to convert the sum back to type `short'.  Here
things get a little fuzzy; comp.std.c has had arguments as to what
happens if the sum is `int', `int' is wider than `short', and the
sum is not representable as a `short'.  The Standard distinguishes
between `implicit' and `explicit' conversions, and this is an
`implicit' conversion (casts are the usual, perhaps the only, way
to write explicit conversions).  Some have argued that an explicit
conversion should never cause an overflow, but there seems to be
no wording in the standard to justify this position.  There appears
to be agreement that implicit conversion might overflow.  The
behavior on overflow would be undefined.

Your particular compiler is likely to ignore the overflow and
truncate the result (most compilers do, and the warning -- `possible
loss of data' -- suggests that yours does).

It is interesting, however, to note that if overflow will ever occur,
it will occur no matter whether the ranges of short and int coincide
or not.  The only thing that changes here is the exact operation that
overflows: the sum, or the implicit conversion in the assignment.

The compiler's warning suggests (but by no means guarantees) that
this particular compiler has wider `int's than `short's.  Since this
is comp.lang.c.moderated (as well as comp.std.c), it is reasonable
to ask whether the warning the compiler emits is `reasonable'.  I
think not, but this is purely my opinion.  The Standard itself allows
arbitrary warnings, so it is no help here.

>my compiler (Microsoft Visual C++ 4.0), automagically converts 
>my short-parameters to int's, even though all variables involved 
>are short. I know that the standard says that all arguments can 
>be converted to the biggest 'type' of all the arguments,

See the detailed explanation above :-)

>but is it correct that char's and short's always are promoted to int's, 
>without regard to the other arguments in the expression?

In expressions, yes.

>The reason I ask is that I am writing code that should be 
>portable from a 16-bits environment (where short and int are the 
>same size) to a 32-bits environment. (My 16-bits compiler does 
>not complain about the code).

Perhaps it should, since the sum itself can overflow here just as
easily as a conversion from a 32-bit-int sum to a 16-bit short
`result'.  Again, the Standard is no help here, and my personal
opinion is that a warning is unwarranted (because such a warning
should then be given for virtually every operator).

I would find an `assignment may truncate' warning appropriate, and
maybe even helpful, when the target of a complex expression has a
narrower type than at least one of the operands *before* promotion.
That is, if someone writes:

	short a, b, prod;

	prod = a * b;

I would object (mildly) to a warning, but if they had:

	int a;
	short b, prod;

	prod = a * b;

I would not.  In this case, I think an explicit truncation somewhere
should eliminate the warning, so that either:

	prod = (short)(a * b);

or:

	prod = (short)a * b;	/* not the same thing at all! */

should not warn.  But, again, this is simply my opinion.
-- 
In-Real-Life: Chris Torek, Berkeley Software Design Inc
El Cerrito, CA	Domain:	torek@bsdi.com	+1 510 234 3167
Technical Details
Message-ID: <4fu835$9de@solutions.solon.com>
Path: arc1-ca.pugleaf.net!solon.com!not-for-mail
References: <4fstj7$2l6@solutions.solon.com>

Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!solon.com!not-for-mail
From: Chris Torek <torek@elf.bsdi.com>
Newsgroups: comp.std.c,comp.lang.c.moderated
Subject: Re: Integral promotion.
Date: 14 Feb 1996 21:08:21 -0600
Organization: Berkeley Software Design, Inc.
Sender: clc@solutions.solon.com
Approved: clc@solutions.solon.com
Message-ID: <4fu835$9de@solutions.solon.com>
References: <4fstj7$2l6@solutions.solon.com>
Reply-To: torek@bsdi.com
NNTP-Posting-Host: solutions.solon.com