integer promotion

M

Mark

Hello,

#include <stdint.h>
uint8_t bitsLow;
uint16_t bitsHigh;
uint32_t statusBits;
....
bitsHigh = (statusBits >> 8) & 0xffff;
bitsLow = statusBits & 0xff;

Does it make any sense from perspective of integer promotion rules in
following:

bitsHigh = (statusBits >> 8) & 0xffffU;
bitsLow = statusBits & 0xffU;
(as I understand 0xff or 0xffff has by default integer type)

Thanks.

Mark
 
J

James Kuyper

Hello,

#include <stdint.h>
uint8_t bitsLow;
uint16_t bitsHigh;
uint32_t statusBits;
...
bitsHigh = (statusBits >> 8) & 0xffff;
bitsLow = statusBits & 0xff;

Does it make any sense from perspective of integer promotion rules in
following:

bitsHigh = (statusBits >> 8) & 0xffffU;
bitsLow = statusBits & 0xffU;
(as I understand 0xff or 0xffff has by default integer type)

0xff does have integer type. However, INT_MAX can be as low as 0x7fff,
and if 0xffff > INT_MAX, then 0xffff will have the type 'unsigned int'.
This does not apply to decimal constants; they only have unsigned types
if you explicitly use the 'U' suffix.
 
E

Eric Sosman

Hello,

#include <stdint.h>
uint8_t bitsLow;
uint16_t bitsHigh;
uint32_t statusBits;
...
bitsHigh = (statusBits >> 8) & 0xffff;
bitsLow = statusBits & 0xff;

Does it make any sense from perspective of integer promotion rules in
following:

bitsHigh = (statusBits >> 8) & 0xffffU;
bitsLow = statusBits & 0xffU;
(as I understand 0xff or 0xffff has by default integer type)

The type of 0xff is int, always.

The type of 0xffff is either int or unsigned int. If
INT_MAX >= 0xffff the type is int, but if INT_MAX < 0xffff
(as on a system with 16-bit int), the type is unsigned int.
Either way the value is positive, never negative.

Both 0xffU and 0xffffU are of type unsigned int, always.

Does appending the U change the way things promote? Yes,
perhaps, depending on the width of int. There will be "no
practical difference," though.

In your example, all the AND-ing is unnecessary because
the conversion to uint8_t or uint16_t discards exactly those
bits that are zeroed by the AND (either form). You could
have written

bitsHigh = statusBits >> 8;
bitsLow = statusBits;

.... with exactly the same final result. (You'd still need
some AND-ing if you didn't want exactly 16 or exactly 8 bits,
of course.)
 
T

Tim Rentsch

Mark said:
Hello,

#include <stdint.h>
uint8_t bitsLow;
uint16_t bitsHigh;
uint32_t statusBits;
...
bitsHigh = (statusBits >> 8) & 0xffff;
bitsLow = statusBits & 0xff;

Does it make any sense from perspective of integer promotion rules in
following:

bitsHigh = (statusBits >> 8) & 0xffffU;
bitsLow = statusBits & 0xffU;
(as I understand 0xff or 0xffff has by default integer type)

The results will be the same with or without the U's on
the constants.

Given that the source is non-negative (guaranteed because
its type is unsigned) and the targets have unsigned type,
and the right widths, you could just write this:

bitsHigh = statusBits / 256;
bitsLow = statusBits % 256;

These days almost any production compiler will produce code
for these lines that is at least as good as that produced
for the shift/mask version.
 
J

James Kuyper

Thanks for reply.
So basically in these cases we could avoid masking at all ?

Well, for positive values, & 0xFF is equivalent to % 256, so it's not
really correct to say that you've avoided it in that case.

The key point here is that conversion of statusBits>>8 into a uint8_t is
exactly equivalent to masking. You do sometimes need to mask for this
kind of operation, but only if there's a possibility of non-zero bits
that would be removed by the mask, and only if the destination type is
big enough to store these bits.
 
K

Keith Thompson

Mark said:
(as I understand 0xff or 0xffff has by default integer type)

Be careful with terminology. Yes, 0xff and 0xffff have integer
type -- not just by default, but always. int, unsigned int,
unsigned long, unsigned long long, short, signed char, and _Bool,
among others, are all integer types.

What you mean, I think, is that they have type int. The word "int"
originated as an abbreviation of "integer"; that doesn't mean that
"int" and "integer" are synonymous or interchangeable. (See also
"const" and "constant", which have quite different meanings.)

As James Kuyper pointed out in another followup, 0xffff can have
type unsigned int on a system where int is 16 bits (an so INT_MAX
is 32767, or 0x7fff).

It's also worth noting, though it's not directly relevant to your
question, that the rules are different for decimal vs. hexadecimal
constants. A decimal constant has type int, long int, or long long
int (all signed types); specifically, its type is the narrowest type
able to represent its value. A hexadecimal or octal constant has
type int, unsigned int, long int, unsigned long int, long long int,
or unsigned long long int.

So if INT_MAX == 32767, then 0xffff is of type unsigned int, but
65535 (which has the same mathematical value) is of type long.
 
M

Mark

But what if a specific architecture doesn't have modulo instruction and
should be replaced with several instruction versus bitshift/mask that is
easy ?
 
J

James Kuyper

But what if a specific architecture doesn't have modulo instruction and
should be replaced with several instruction versus bitshift/mask that is
easy ?

You can reasonably expect a C implementation to produce equivalent code
(and possibly the exact same code) for & 0xff and % 256. This isn't
required by the standard, but it is a very standard optimization. If it
generates code for one construct that is substantially slower than that
generated for the other, you've got good grounds for complaint against
the vendor.
 
E

Eric Sosman

gibberish.
with no context to help them understand it might as well be
really tangled spaghetti code. People see your response, but
A: Because in the normal way of reading, it's as confusing as
Q: Why?
you're responding to.
A: Top-posting, that is, putting the response before the material
Q: What annoys you about my Usenet posts?

But what if a specific architecture doesn't have modulo instruction and
should be replaced with several instruction versus bitshift/mask that is
easy ?

Tim Rentsch said:
[...]

bitsHigh = statusBits / 256;
bitsLow = statusBits % 256;

Long ago compilers ran on slow machines and in small memory
spaces, and the amount of analysis they could do to optimize code
was limited. In those days, you sometimes had to help the compiler
find a good choice of instructions: You wrote shifts instead of
multiplications and divisions, you unrolled loops manually, you
used the `register' keyword, and so on.

In those days, your "car" had a "carburetor," and sometimes
a "manual choke" that required your attention. (Somewhat earlier
still, you had to worry about adjusting the ignition timing, too.)

Hobbyists may still have wistful memories of rebuilding the
engine on a '48 Pontiac, but drivers who just want to get places
without a lot of fuss prefer modern, chokeless, fuel-injected
engines that operate well under a wide variety of conditions and
don't require endless fuss-budgeting.

Guess what? Compilers aren't what they were thirty years
ago, either. They know more about their target machines than
most people will ever learn, and they've got enoough CPU cycles
and memory to apply that knowledge. You're wasting your time
trying to outsmart them.

Concentrate on your problem -- plan a route, maintain a safe
speed, pay attention to traffic issues -- and let the compiler
attend to burning the gasoline.
 
K

Keith Thompson

James Kuyper said:
You can reasonably expect a C implementation to produce equivalent code
(and possibly the exact same code) for & 0xff and % 256. This isn't
required by the standard, but it is a very standard optimization. If it
generates code for one construct that is substantially slower than that
generated for the other, you've got good grounds for complaint against
the vendor.

And specifically, if the CPU doesn't have a modulo instruction, the
compiler is even more likely to replace a "%" by a constant power of 2
by the equivalent shift.

(That optimization isn't going to be possible for "% 255", or for "% x"
where "x" happens to equal 256 but the compiler can't determine that at
compile time.)
 
T

Tim Rentsch

This is a combined response to two postings. Note that it
is customary in newsgroup responses to put the responding
comments after the comments being responded to, not before.
It would help if you could follow that custom.
Thanks for reply.
So basically in these cases we could avoid masking at all ?

We are avoiding use of the '&' ("masking") operator. Whether
masking takes place in the underlying hardware, or the operation
is done by some other means, is not known in general. Nor is it
important in most cases.

If we have a variable foo with 'foo >= 0' (and width at least
16 in the last case), then

foo % 256 == (foo & 255)
foo / 256 == foo >> 8
foo % 65536 == (foo & 65535)
foo / 65536 == foo >> 16

These relationships hold in all (conforming) C implementations.
If 'foo' is of unsigned integer type, then its value is always
greater than or equal to zero, so these relationships always
hold for unsigned types (again assuming in the last case that
the type being operated on with >> is at least 16 bits wide).

If we combine the second and third lines, we get

foo / 256 % 65536 == (foo >> 8 & 65535)

Again this relationship always holds if foo >= 0.

But what if a specific architecture doesn't have modulo
instruction and should be replaced with several instruction
versus bitshift/mask that is easy ?

First it's important to understand what sort of difference we're
talking about. The two approaches yield the same results,
numerically speaking; the only potential difference is how
fast the resulting programs will run.

These days, most compilers will do a better job than people of
picking good (meaning fast, among other things) instruction
sequences, and compilers are more knowledgeable (and reliable) in
their understanding of the differences between different machine
architecures than people are. There /are/ cases where it's
important to identify particular instruction sequences for a
particular architectural target; /but/, nowadays it is extremely
rare for that to manifest at the level of writing C source code.
So unless you're in a very unusual situation, and know exactly
what you're doing and why, it's better to pick source code that
expresses what you're trying to do as simply and directly as it
can. So, rather than think about what operations the _machine_
can do, think about what _you_ are trying to accomplish in terms
of the values involved, and write code keeping that second thing
in mind instead of the first.

Does that all make sense?
 
G

glen herrmannsfeldt

Keith Thompson said:
And specifically, if the CPU doesn't have a modulo instruction, the
compiler is even more likely to replace a "%" by a constant power of 2
by the equivalent shift.
(That optimization isn't going to be possible for "% 255", or for "% x"
where "x" happens to equal 256 but the compiler can't determine that at
compile time.)

It seems to be common to replace a divide by constant with the high
half of the product with an appropriate constant. Generating x%y
with the usual x-x/y*y (for constant y) using the appropriate multiply
instructions would seem to be possible. I don't know how the speed
would compare to an actual divide (with quotient and remainder)
instruction.

-- glen
 
R

Rosario1903

Hobbyists may still have wistful memories of rebuilding the
engine on a '48 Pontiac, but drivers who just want to get places
without a lot of fuss prefer modern, chokeless, fuel-injected
engines that operate well under a wide variety of conditions and
don't require endless fuss-budgeting.

Guess what? Compilers aren't what they were thirty years
ago, either. They know more about their target machines than
most people will ever learn, and they've got enoough CPU cycles
and memory to apply that knowledge. You're wasting your time
trying to outsmart them.

Concentrate on your problem -- plan a route, maintain a safe
speed, pay attention to traffic issues -- and let the compiler
attend to burning the gasoline.

i can not be agree because i trust more
my instructios than compilers ones
 
E

Eric Sosman

i can not be agree because i trust more
my instructios than compilers ones

It is possible that your trust is well-placed.

It's not probable, but it's possible.

It's not even likely, but it's still possible.

It's so unlikely as to stretch the bounds of credulity,
but it's possible nonetheless.

Yes, I must admit: It *is* possible. Good luck!
 
T

Tim Rentsch

Nick Bowler said:
Nit: It is impossible for >> to operate on a type that is not at
least 16 bits wide, since any smaller type would necessarily
promote to int, which is always at least 16 bits wide. So the
right shift by 16 is always safe for non-negative foo, regardless
of its type.

I don't know what I was thinking when I wrote the earlier remark,
unless possibly it could have been that a width of "at least 16"
meant a width of 17 or greater. In any case the shift operation
'foo >> 16' has undefined behavior unless the width of foo (after
promotion) is strictly greater than 16.

As a consequence, 'foo >> 16' has undefined behavior for types no
larger than int in those implementations where unsigned int and
int have a width of 16 bits, and also for types that promote to
int in those implemenations where int has a width of 16 bits,
regardless of the width of unsigned int. In general, if the
type is not known, the largest shift value that may be used
safely across all implementations is 15, not 16.
 
K

Keith Thompson

Nick Bowler said:
Nit: It is impossible for >> to operate on a type that is not at least
16 bits wide, since any smaller type would necessarily promote to int,
which is always at least 16 bits wide.
Right.

So the right shift by 16 is
always safe for non-negative foo, regardless of its type.

Not quite. N1570 6.5.7p3 (emphasis added):

The integer promotions are performed on each of the operands. The
type of the result is that of the promoted left operand. If the
value of the right operand is negative or is greater than *or
equal to* the width of the promoted left operand, the behavior
is undefined.
 
G

glen herrmannsfeldt

Keith Thompson said:
(snip)

(snip)
Not quite. N1570 6.5.7p3 (emphasis added):
The integer promotions are performed on each of the operands. The
type of the result is that of the promoted left operand. If the
value of the right operand is negative or is greater than *or
equal to* the width of the promoted left operand, the behavior
is undefined.

And yes, many (most) machines treat shifts modulo the length.

The 8088 and 8086 do shifts modulo 256, but that was changed
in later processors.

In the development of the Hercules S/360 emulator, I noticed that
they had this wrong. S/360 does 32 (and 64) bit shifts modulo 64.
Using the C shift operators on 32 bit words got this wrong.
(The test at the time was trying to run OS/360 on it.)

-- glen
 
K

Keith Thompson

glen herrmannsfeldt said:
And yes, many (most) machines treat shifts modulo the length.

The 8088 and 8086 do shifts modulo 256, but that was changed
in later processors.

In the development of the Hercules S/360 emulator, I noticed that
they had this wrong. S/360 does 32 (and 64) bit shifts modulo 64.
Using the C shift operators on 32 bit words got this wrong.
(The test at the time was trying to run OS/360 on it.)

It's not "wrong" as far as the C standard is concerned.

For that matter, since "<< 31" is equivalent to doing "<< 1" 31
times, it would make sense for "<< 32" to be equivalent to doing
"<< 1" 32 times. Making it an identity operation seems odd.

In any case, the failure to agree on what it should mean is probably
why C doesn't define the behavior.
 
G

glen herrmannsfeldt

(snip on shift operations equal to or longer than the operand
being shifted, then I wrote)
It's not "wrong" as far as the C standard is concerned.

Using shift amounts 32 or greater on 32 bit int, and expecting
a specific result, caused OS/360 to fail.
For that matter, since "<< 31" is equivalent to doing "<< 1" 31
times, it would make sense for "<< 32" to be equivalent to doing
"<< 1" 32 times. Making it an identity operation seems odd.

I presume somewhere in the early days of computers, some did
shift for the specified amount. For S/360, the shift amount
is the low 6 bits of the sum of two 32 bit registers and
a 12 bit unsigned constant. If you didn't limit it, user
programs could go off on an endless (or greater than MTBF,
whichever comes first) loop.

As noted, the 8086 and 8088 allow up to 255. You can count the
processor cycles used. Many others take the low five bits for 32 bit
shifts. (I believe including all later x86 processors.)
In any case, the failure to agree on what it should mean is probably
why C doesn't define the behavior.

If you want different behavior, you can always do it using C.
If you never have larger shift amounts, there shouldn't be the
overhead of C checking for it.

-- glen
 

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,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top