Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.c > #91781 > unrolled thread
| Started by | Real Troll <real.troll@trolls.com> |
|---|---|
| First post | 2016-08-05 18:45 -0400 |
| Last post | 2016-08-09 11:35 -0700 |
| Articles | 20 on this page of 52 — 20 participants |
Back to article view | Back to comp.lang.c
sizeof Data types Real Troll <real.troll@trolls.com> - 2016-08-05 18:45 -0400
sizeof Data types jameskuyper@verizon.net - 2016-08-05 17:10 -0700
Re: sizeof Data types raltbos@xs4all.nl (Richard Bos) - 2016-08-06 10:35 +0000
Re: sizeof Data types raltbos@xs4all.nl (Richard Bos) - 2016-08-06 10:36 +0000
Re: sizeof Data types Jean-Marc Bourguet <jm@bourguet.org> - 2016-08-06 22:29 +0200
Re: sizeof Data types Ken Brody <kenbrody@spamcop.net> - 2016-08-09 12:55 -0400
Re: sizeof Data types BartC <bc@freeuk.com> - 2016-08-06 21:43 +0100
Re: sizeof Data types jameskuyper@verizon.net - 2016-08-06 15:25 -0700
Re: sizeof Data types David Brown <david.brown@hesbynett.no> - 2016-08-08 09:48 +0200
Re: sizeof Data types James Kuyper <jameskuyper@verizon.net> - 2016-08-08 11:05 -0400
Re: sizeof Data types David Brown <david.brown@hesbynett.no> - 2016-08-08 17:57 +0200
Re: sizeof Data types supercat@casperkitty.com - 2016-08-08 08:36 -0700
Re: sizeof Data types David Brown <david.brown@hesbynett.no> - 2016-08-08 19:05 +0200
Re: sizeof Data types supercat@casperkitty.com - 2016-08-08 14:09 -0700
Re: sizeof Data types David Brown <david.brown@hesbynett.no> - 2016-08-09 09:38 +0200
Re: sizeof Data types Ben Bacarisse <ben.usenet@bsb.me.uk> - 2016-08-09 11:23 +0100
Re: sizeof Data types Tim Rentsch <txr@alumni.caltech.edu> - 2016-08-12 07:42 -0700
Re: sizeof Data types Ben Bacarisse <ben.usenet@bsb.me.uk> - 2016-08-12 21:38 +0100
Re: sizeof Data types Keith Thompson <kst-u@mib.org> - 2016-08-09 08:12 -0700
Re: sizeof Data types supercat@casperkitty.com - 2016-08-10 10:36 -0700
Re: sizeof Data types Keith Thompson <kst-u@mib.org> - 2016-08-10 11:42 -0700
Re: sizeof Data types Philip Lantz <prl@canterey.us> - 2016-08-11 10:20 -0700
Re: sizeof Data types Keith Thompson <kst-u@mib.org> - 2016-08-11 12:04 -0700
Re: sizeof Data types Tim Rentsch <txr@alumni.caltech.edu> - 2016-08-12 08:22 -0700
Re: sizeof Data types Nobody <nobody@nowhere.invalid> - 2016-08-07 01:32 +0100
Re: sizeof Data types Keith Thompson <kst-u@mib.org> - 2016-08-06 18:10 -0700
Re: sizeof Data types Siri Cruise <chine.bleu@yahoo.com> - 2016-08-06 20:22 -0700
Re: sizeof Data types Ian Collins <ian-news@hotmail.com> - 2016-08-07 15:40 +1200
Re: sizeof Data types Ken Brody <kenbrody@spamcop.net> - 2016-08-09 13:06 -0400
Re: sizeof Data types Siri Cruise <chine.bleu@yahoo.com> - 2016-08-09 12:37 -0700
Re: sizeof Data types Tim Rentsch <txr@alumni.caltech.edu> - 2016-08-11 09:18 -0700
Re: sizeof Data types Siri Cruise <chine.bleu@yahoo.com> - 2016-08-11 11:11 -0700
Re: sizeof Data types Joe Pfeiffer <pfeiffer@cs.nmsu.edu> - 2016-08-11 12:26 -0600
Re: sizeof Data types David Brown <david.brown@hesbynett.no> - 2016-08-11 23:31 +0200
Re: sizeof Data types supercat@casperkitty.com - 2016-08-11 14:59 -0700
Re: sizeof Data types Malcolm McLean <malcolm.mclean5@btinternet.com> - 2016-08-12 00:15 -0700
Re: sizeof Data types Tim Rentsch <txr@alumni.caltech.edu> - 2016-08-12 08:05 -0700
Re: sizeof Data types John Bode <jfbode1029@gmail.com> - 2016-08-08 12:38 -0700
Re: sizeof Data types Ken Brody <kenbrody@spamcop.net> - 2016-08-09 13:08 -0400
Re: sizeof Data types Keith Thompson <kst-u@mib.org> - 2016-08-09 10:43 -0700
Re: sizeof Data types "James R. Kuyper" <jameskuyper@verizon.net> - 2016-08-09 13:41 -0400
Re: sizeof Data types Ken Brody <kenbrody@spamcop.net> - 2016-08-10 11:51 -0400
Re: sizeof Data types "James R. Kuyper" <jameskuyper@verizon.net> - 2016-08-10 12:35 -0400
Re: sizeof Data types Ken Brody <kenbrody@spamcop.net> - 2016-08-11 11:53 -0400
Re: sizeof Data types "James R. Kuyper" <jameskuyper@verizon.net> - 2016-08-13 11:29 -0400
Re: sizeof Data types supercat@casperkitty.com - 2016-08-13 09:18 -0700
Re: sizeof Data types Keith Thompson <kst-u@mib.org> - 2016-08-10 10:13 -0700
Re: sizeof Data types Ken Brody <kenbrody@spamcop.net> - 2016-08-11 11:56 -0400
Re: sizeof Data types Ken Brody <kenbrody@spamcop.net> - 2016-08-09 12:59 -0400
Re: sizeof Data types "James R. Kuyper" <jameskuyper@verizon.net> - 2016-08-09 13:49 -0400
Re: sizeof Data types Ken Brody <kenbrody@spamcop.net> - 2016-08-10 12:08 -0400
Re: sizeof Data types Keith Thompson <kst-u@mib.org> - 2016-08-09 11:35 -0700
Page 1 of 3 [1] 2 3 Next page →
| From | Real Troll <real.troll@trolls.com> |
|---|---|
| Date | 2016-08-05 18:45 -0400 |
| Subject | sizeof Data types |
| Message-ID | <no34bv$1rus$1@gioia.aioe.org> |
Does this look right for various basic data types: > Storage size for char : 1 > Storage size for signed char : 1 > Storage size for unsigned char : 1 > Storage size for int : 4 > Storage size for unsigned int : 4 > Storage size for float : 4 > Storage size for double : 8 > Storage size for short : 2 > Storage size for unsigned short : 2 > Storage size for long : 4 > Storage size for unsigned long : 4
[toc] | [next] | [standalone]
| From | jameskuyper@verizon.net |
|---|---|
| Date | 2016-08-05 17:10 -0700 |
| Message-ID | <52e9225f-f44f-4044-bd38-39800940762f@googlegroups.com> |
| In reply to | #91781 |
The first three numbers are required to be 1; for a sufficiently large value of CHAR_BIT, any positive integer could be "right" for any of the other numbers. For CHAR_BIT==8, those are pretty much the typical values. You shouldn't write code that makes any assumptions about those values other than the first three - that's what sizeof is for.
[toc] | [prev] | [next] | [standalone]
| From | raltbos@xs4all.nl (Richard Bos) |
|---|---|
| Date | 2016-08-06 10:35 +0000 |
| Message-ID | <57a5bd3f.4428515@news.xs4all.nl> |
| In reply to | #91781 |
Real Troll <real.troll@trolls.com> wrote: > Does this look right for various basic data types: > > > Storage size for char : 1 > > Storage size for signed char : 1 > > Storage size for unsigned char : 1 Yes, for these. > > Storage size for int : 4 > > Storage size for unsigned int : 4 > > Storage size for float : 4 > > Storage size for double : 8 > > Storage size for short : 2 > > Storage size for unsigned short : 2 > > Storage size for long : 4 > > Storage size for unsigned long : 4 Not necessarily, for these. They may be common, but they're by no means universal. Richard
[toc] | [prev] | [next] | [standalone]
| From | raltbos@xs4all.nl (Richard Bos) |
|---|---|
| Date | 2016-08-06 10:36 +0000 |
| Message-ID | <57a5bd7a.4486843@news.xs4all.nl> |
| In reply to | #91781 |
ram@zedat.fu-berlin.de (Stefan Ram) wrote: > Real Troll <real.troll@trolls.com> writes: > >Does this look right for various basic data types: > >>Storage size for char : 1 > > It does not look right; in Usenet we use a ť>Ť at the > beginning of a line to quote something from a post from the > References header line of the post. We also don't indent our own material... Richard
[toc] | [prev] | [next] | [standalone]
| From | Jean-Marc Bourguet <jm@bourguet.org> |
|---|---|
| Date | 2016-08-06 22:29 +0200 |
| Message-ID | <87y4491w03.fsf@news.bourguet.org> |
| In reply to | #91781 |
Real Troll <real.troll@trolls.com> writes: > Does this look right for various basic data types: > >> Storage size for char : 1 >> Storage size for signed char : 1 >> Storage size for unsigned char : 1 Mandatory (as is the equality of size between corresponding signed and unsigned types). >> Storage size for int : 4 >> Storage size for unsigned int : 4 >> Storage size for float : 4 >> Storage size for double : 8 >> Storage size for short : 2 >> Storage size for unsigned short : 2 Common but not universal. >> Storage size for long : 4 >> Storage size for unsigned long : 4 AFAIK, this is false for all 64-bit Unix (I think Windows is the only one which dared to break the property sizeof(void*) <= sizeof(long)). Yours, -- Jean-Marc
[toc] | [prev] | [next] | [standalone]
| From | Ken Brody <kenbrody@spamcop.net> |
|---|---|
| Date | 2016-08-09 12:55 -0400 |
| Message-ID | <nod1uq$s5e$1@dont-email.me> |
| In reply to | #91829 |
On 8/6/2016 4:29 PM, Jean-Marc Bourguet wrote: > Real Troll <real.troll@trolls.com> writes: > >> Does this look right for various basic data types: [...] >>> Storage size for long : 4 >>> Storage size for unsigned long : 4 > > AFAIK, this is false for all 64-bit Unix (I think Windows is the only one > which dared to break the property sizeof(void*) <= sizeof(long)). My guess is that MS felt it was "more important" that sizeof(long) remained 4, so as to not break too much existing code which assumed it. (Yes, people shouldn't have assumed it, or they used "long" rather than one of the MS types such as "DWORD", but they did, and MS didn't want to break "working" code.) Confession: Some legacy code I maintain used a "long" to store a pointer, which of course broke once we ported to 64-bit Windows. (Worked just fine on 64-bit Linux.) Actually, it would "sometimes" hold a pointer, and other times it would hold an integer, which is why it wasn't a pointer to begin with. (I didn't write that part, so don't blame me.) I found all references to it, and changed it to "intptr_t" instead. -- Kenneth Brody
[toc] | [prev] | [next] | [standalone]
| From | BartC <bc@freeuk.com> |
|---|---|
| Date | 2016-08-06 21:43 +0100 |
| Message-ID | <no5i6b$fk2$1@dont-email.me> |
| In reply to | #91781 |
On 05/08/2016 23:45, Real Troll wrote:
>
> Does this look right for various basic data types:
>
>> Storage size for char : 1
>> Storage size for signed char : 1
>> Storage size for unsigned char : 1
>> Storage size for int : 4
>> Storage size for unsigned int : 4
>> Storage size for float : 4
>> Storage size for double : 8
>> Storage size for short : 2
>> Storage size for unsigned short : 2
>> Storage size for long : 4
>> Storage size for unsigned long : 4
Signed and unsigned versions of any type will almost certainly be the
same size.
You forgot long long which is usually size 8. The full set, with likely
sizes on typical consumer PCs, is:
sizeof:
char 1
short 2
int 4
long 4 or 8
long long 8
float 4
double 8
long double (don't know)
T* (pointer) 4 or 8
If you need to have confidence as to how big exactly short, int, long or
long long are, use instead the types specified in stdint.h: int32_t,
uint16_t etc.
You can't override the pointer size.
(You might notice an inconsistency in the table above, where the 4
widths usually available, 1, 2, 4 and 8 bytes, are shared by 5 basic
types. This is because 'long' can't make up its mind what it is. For
that reason, I never use that.)
--
Bartc
[toc] | [prev] | [next] | [standalone]
| From | jameskuyper@verizon.net |
|---|---|
| Date | 2016-08-06 15:25 -0700 |
| Message-ID | <4ccb69e9-3af1-4bf7-8893-773508c03bf9@googlegroups.com> |
| In reply to | #91830 |
On Saturday, August 6, 2016 at 4:44:03 PM UTC-4, Bart wrote: > On 05/08/2016 23:45, Real Troll wrote: > > > > Does this look right for various basic data types: > > > >> Storage size for char : 1 > >> Storage size for signed char : 1 > >> Storage size for unsigned char : 1 > >> Storage size for int : 4 > >> Storage size for unsigned int : 4 > >> Storage size for float : 4 > >> Storage size for double : 8 > >> Storage size for short : 2 > >> Storage size for unsigned short : 2 > >> Storage size for long : 4 > >> Storage size for unsigned long : 4 > > Signed and unsigned versions of any type will almost certainly be the > same size. It is a certainty for any conforming implementation (and the variety of options allowed for non-conforming implementations is too large to be meaningfully discussed - it includes, among other things, sizeof(long) == "Too large"). "For each of the signed integer types, there is a corresponding (but different) unsigned integer type (designated with the keyword unsigned) that uses the same amount of storage (including sign information) and has the same alignment requirements." (6.2.5p6) > You forgot long long which is usually size 8. It's not clear whether he forgot it, because he hasn't explained the purpose of his question. It's possible he's looking for a list of types compatible with C90. > ... The full set, How do you define "full set"? Your list comes close to including all of C's scalar types (excluding the extended types, which are incapable of being listed) - but it also missing one standard integer type and three of the floating point types (the three that are missing are, admittedly, optional). > ... with likely sizes on typical consumer PCs, is: > > sizeof: > > char 1 > short 2 > int 4 > long 4 or 8 > long long 8 > > float 4 > double 8 > long double (don't know) > > T* (pointer) 4 or 8 > > If you need to have confidence as to how big exactly short, int, long or > long long are, use instead the types specified in stdint.h: int32_t, > uint16_t etc. "If you need to have confidence as to exactly how big" one of the standard types is, you should use sizeof find out. I think you mean "if you need an integer type with a specified size". Note that int32_t, etc. need not be any standard type - they could all be extended types - but if you need an exactly 32-bit type, int32_t is it (if supported - and if it isn't supported, you're trying to do something that can't be done on that platform). > > You can't override the pointer size. > > (You might notice an inconsistency in the table above, where the 4 > widths usually available, 1, 2, 4 and 8 bytes, are shared by 5 basic > types. This is because 'long' can't make up its mind what it is. For > that reason, I never use that.) No, it's because "int" and "short" have the same minimum range, leaving sizeof(int)==2 and sizeof(int)==4 to be two different popular options. My choice of data type is usually constrained by the interfaces to the libraries I have to work with. However, when I'm free of such constraints, I use "int" as if it were a short form for int_fast16_t, and I use long as if it were a short form for int_least32_t. What would you use if you needed one of those two types?
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2016-08-08 09:48 +0200 |
| Message-ID | <no9dgf$a05$1@dont-email.me> |
| In reply to | #91831 |
On 07/08/16 00:25, jameskuyper@verizon.net wrote: > My choice of data type is usually constrained by the interfaces to > the libraries I have to work with. However, when I'm free of such > constraints, I use "int" as if it were a short form for int_fast16_t, > and I use long as if it were a short form for int_least32_t. What > would you use if you needed one of those two types? > How about using int_fast16_t when you want a short form for int_fast16_t, and int_least32_t when you want a short form for int_least32_t? Those types exist (in C99 onwards) not just for consistency with the other intX_t types, but because they can be useful in getting the most efficient types with the properties you want. "int" is likely to be as efficient as "int_fast16_t" for single objects, but if you are using large arrays then "int_fast16_t" will be faster on most platforms (by making more efficient use of the cache). "long" will satisfy the requirements of being at least 32 bits, but "int_least32_t" is likely to be more efficient in most cases. And the intX_t types are more explicit about the exact requirements you are looking for. It is, of course, up to you and your code to decide how that balances against the greater verbosity (and some would say, ugliness) of the the intX_t types compared to clean and simple "int" and "long".
[toc] | [prev] | [next] | [standalone]
| From | James Kuyper <jameskuyper@verizon.net> |
|---|---|
| Date | 2016-08-08 11:05 -0400 |
| Message-ID | <noa74c$rv2$1@dont-email.me> |
| In reply to | #91923 |
On 08/08/2016 03:48 AM, David Brown wrote: > On 07/08/16 00:25, jameskuyper@verizon.net wrote: > >> My choice of data type is usually constrained by the interfaces to >> the libraries I have to work with. However, when I'm free of such >> constraints, I use "int" as if it were a short form for int_fast16_t, >> and I use long as if it were a short form for int_least32_t. What >> would you use if you needed one of those two types? >> > > > How about using int_fast16_t when you want a short form for > int_fast16_t, and int_least32_t when you want a short form for > int_least32_t? Because "int" IS shorter than "int_fast16_t", and "int_fast16_t" isn't. > Those types exist (in C99 onwards) not just for consistency with the > other intX_t types, but because they can be useful in getting the most > efficient types with the properties you want. "int" is likely to be as > efficient as "int_fast16_t" for single objects, but if you are using > large arrays then "int_fast16_t" will be faster on most platforms (by > making more efficient use of the cache). "long" will satisfy the > requirements of being at least 32 bits, but "int_least32_t" is likely to > be more efficient in most cases. I would be surprised to see int_fast16_t being significantly faster than int. I'd be less surprised to see int_least32_t being smaller than 'long' - I know that there are platforms where 'long' is 64 bits, and on many of those platforms, int_least32_t will be a 32-bit type. However, I'm used to being surprised, it wouldn't bother me much. If I were aware of such being the case, of course I'd use the size-named types. My preference for "int" and "long" is a minor bit of laziness, not something that I'd let interfere with a significantly better choice, if I were certain it was significantly better.
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2016-08-08 17:57 +0200 |
| Message-ID | <noaa5p$6ti$1@dont-email.me> |
| In reply to | #91958 |
On 08/08/16 17:05, James Kuyper wrote: > On 08/08/2016 03:48 AM, David Brown wrote: >> On 07/08/16 00:25, jameskuyper@verizon.net wrote: >> >>> My choice of data type is usually constrained by the interfaces to >>> the libraries I have to work with. However, when I'm free of such >>> constraints, I use "int" as if it were a short form for int_fast16_t, >>> and I use long as if it were a short form for int_least32_t. What >>> would you use if you needed one of those two types? >>> >> >> >> How about using int_fast16_t when you want a short form for >> int_fast16_t, and int_least32_t when you want a short form for >> int_least32_t? > > Because "int" IS shorter than "int_fast16_t", and "int_fast16_t" isn't. That is sometimes a perfectly good reason - certainly it's the reason /I/ normally use. But it is not always the most important reason for choosing between the two types. > >> Those types exist (in C99 onwards) not just for consistency with the >> other intX_t types, but because they can be useful in getting the most >> efficient types with the properties you want. "int" is likely to be as >> efficient as "int_fast16_t" for single objects, but if you are using >> large arrays then "int_fast16_t" will be faster on most platforms (by >> making more efficient use of the cache). "long" will satisfy the >> requirements of being at least 32 bits, but "int_least32_t" is likely to >> be more efficient in most cases. > > I would be surprised to see int_fast16_t being significantly faster than > int. We used to make several systems which had a 32-bit processor (and 32-bit "int"), but only a 16-bit external databus to memory. 16-bit values were significantly faster to work with if the value had to be stored in memory rather than in registers. But for more realistic machines, you will see a difference if you are working with enough of such values. Cache hit/miss ratios can have a significant effect on the speed of programs, and using 32-bit variables where 16-bit would do effectively halves your data cache. > I'd be less surprised to see int_least32_t being smaller than > 'long' - I know that there are platforms where 'long' is 64 bits, and on > many of those platforms, int_least32_t will be a 32-bit type. That would be all (or at least, almost all) 64-bit *nix systems. And again, the biggest effect would be on cache usage. > However, > I'm used to being surprised, it wouldn't bother me much. If I were aware > of such being the case, of course I'd use the size-named types. My > preference for "int" and "long" is a minor bit of laziness, not > something that I'd let interfere with a significantly better choice, if > I were certain it was significantly better. > Of course. And I would say that "int" and "long" are easier to read, as well as easier to write - some kinds of laziness are good. And with single variables, especially local variables that might be entirely in registers, it is highly unlikely that "int" and "long" will be slower. But it's perhaps worth remembering the possible effects if you are using large data sets (and if speed is an issue) - cache usage is often key to performance.
[toc] | [prev] | [next] | [standalone]
| From | supercat@casperkitty.com |
|---|---|
| Date | 2016-08-08 08:36 -0700 |
| Message-ID | <450a4877-bb68-48d5-b24e-123ca59569a1@googlegroups.com> |
| In reply to | #91923 |
On Monday, August 8, 2016 at 2:48:39 AM UTC-5, David Brown wrote:
> And the intX_t types are more explicit about the exact requirements you
> are looking for. It is, of course, up to you and your code to decide
> how that balances against the greater verbosity (and some would say,
> ugliness) of the the intX_t types compared to clean and simple "int" and
> "long".
They pretend to be explicit, but their practical behavior is still very much
dependent upon the size of "int". Consider:
int diffcomp(uint16_t a, uint16_b, uint16_t c) { return a-b > c; }
What should be the result of diffcomp(1,2,3);
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2016-08-08 19:05 +0200 |
| Message-ID | <noae50$l9r$1@dont-email.me> |
| In reply to | #91963 |
On 08/08/16 17:36, supercat@casperkitty.com wrote:
> On Monday, August 8, 2016 at 2:48:39 AM UTC-5, David Brown wrote:
>> And the intX_t types are more explicit about the exact requirements you
>> are looking for. It is, of course, up to you and your code to decide
>> how that balances against the greater verbosity (and some would say,
>> ugliness) of the the intX_t types compared to clean and simple "int" and
>> "long".
>
> They pretend to be explicit, but their practical behavior is still very much
> dependent upon the size of "int".
They /are/ explicit in their sizes. But the exact behaviour in some
circumstances is implementation (and therefore also target) dependent.
> Consider:
>
> int diffcomp(uint16_t a, uint16_b, uint16_t c) { return a-b > c; }
>
> What should be the result of diffcomp(1,2,3);
>
To save Ian Collins the bother, I would say the result is a failed code
review.
If you don't want implementation dependent behaviour in your programs,
don't write code that depends on the implementation.
int diffcomp(uint16_t a, uint16_b, uint16_t c) {
return (unsigned int) a - b > c;
}
[toc] | [prev] | [next] | [standalone]
| From | supercat@casperkitty.com |
|---|---|
| Date | 2016-08-08 14:09 -0700 |
| Message-ID | <433a7e7e-5f86-4a08-864f-f6074bf53cc8@googlegroups.com> |
| In reply to | #91974 |
On Monday, August 8, 2016 at 12:05:44 PM UTC-5, David Brown wrote:
> If you don't want implementation dependent behaviour in your programs,
> don't write code that depends on the implementation.
>
> int diffcomp(uint16_t a, uint16_b, uint16_t c) {
> return (unsigned int) a - b > c;
> }
The effect of that will still vary depending upon the implementation.
Handling this particular expression in portable fashion requires casting
the arithmetic result, e.g.
int diffcomp(uint16_t a, uint16_b, uint16_t c) {
return (uint16_t)(a - b) > c;
but some expressions may also require coercing intermediate values, e.g.
int mod4294967296mulcomp(uint32_t a, uint32_t b, uint32_t c)
{ return (uint32_t)(1u*a*b) > c; }
The cast is necessary to ensure the value to be compared is reduced mod
4294967296, but the before that can occur it will be necessary on 64-bit
systems to ensure that the result gets coerced to an unsigned type.
While I would regard the casts to the code samples above as improving
human readability and I would favor including them for that reason, I
can't say the same about the multiplication by 1u. I see no reason why
a sane compiler for a silent-wraparound two's-complement machine should
require that cast, but I wouldn't trust gcc to produce usable code without
it, since it does not consistently produce arithmetically-correct code on
silent-wraparound two's-complement platforms with 32-bit int and 16-bit
short, for
unsigned mul(unsigned short a, unsigned short b)
{ return a*b; }
even though the authors of the Standard indicate in the rationale that
they would expect such implementations to treat the multiply as being
equivalent to unsigned.
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2016-08-09 09:38 +0200 |
| Message-ID | <noc19a$d5h$1@dont-email.me> |
| In reply to | #91996 |
On 08/08/16 23:09, supercat@casperkitty.com wrote:
> On Monday, August 8, 2016 at 12:05:44 PM UTC-5, David Brown wrote:
>> If you don't want implementation dependent behaviour in your programs,
>> don't write code that depends on the implementation.
>>
>> int diffcomp(uint16_t a, uint16_b, uint16_t c) {
>> return (unsigned int) a - b > c;
>> }
>
> The effect of that will still vary depending upon the implementation.
Perhaps I have got the details wrong, but my understanding here is:
uint16_t is either "unsigned short", "unsigned int", or an unsigned
extended integer type of lower rank than "unsigned int".
"a" is cast to an unsigned int. This either involves no change at all,
or a safe conversion since the range of unsigned int is at least as big
as that of uint16_t.
Before the "-" operation, the two operands need to be the same type.
Since "unsigned int" has higher or equal rank to "uint16_t", and both
are unsigned types, b is converted/promoted to "unsigned int".
The "-" operation is carried out with unsigned int arithmetic (modular
overflow handling).
Similarly, "c" is converted/promoted as necessary to unsigned int, and
the comparison is done with unsigned int arithmetic.
The result of the comparison is an int, with value 0 or 1.
The legal values for the arguments to the function are all 0..65535, as
that is how uint16_t is defined.
I see no scope for implementation-dependent behaviour here. But if you
or anyone else sees something that I've missed, please say.
> Handling this particular expression in portable fashion requires casting
> the arithmetic result, e.g.
>
> int diffcomp(uint16_t a, uint16_b, uint16_t c) {
> return (uint16_t)(a - b) > c;
That would work too, as far as I can see.
>
> but some expressions may also require coercing intermediate values, e.g.
That is true (though I think you mean "cast", rather than "coerce") -
but you are now talking about a different function and a different
expression. Different functions require different code to be correct -
this should not really surprise anyone.
>
> int mod4294967296mulcomp(uint32_t a, uint32_t b, uint32_t c)
> { return (uint32_t)(1u*a*b) > c; }
>
> The cast is necessary to ensure the value to be compared is reduced mod
> 4294967296, but the before that can occur it will be necessary on 64-bit
> systems to ensure that the result gets coerced to an unsigned type.
On a system with 16-bit int, everything here will be done in unsigned
longs, and no conversions or casts are needed.
On a system with 32-bit int, uint32_t types might be automatically
converted to unsigned int (if they were unsigned shorts, or an extended
integer type), or they may remain as unsigned long (if that's the real
type of uint32_t). Either way, no conversions or casts are needed as
themultiplications are already done as modulo arithmetic.
On a system with 64-bit int and support for uint32_t, then yes - a cast
or an explicit & or % operation will be needed to get the effect you are
asking for here.
But your example is an extremely hypothetical one. First, 64-bit int
systems are rare - the only one I have heard of is some early Cray
systems, and they also had 64-bit short (and therefore no uint32_t).
Secondly, /if/ you were writing such modular arithmetic routines that
had to be portable even to the Cray, you would use separate functions
rather than casting. You would probably also not rely on the existence
of uint32_t, but rather use "unsigned long" and explicit modulos for
greater portability.
>
> While I would regard the casts to the code samples above as improving
> human readability and I would favor including them for that reason, I
> can't say the same about the multiplication by 1u. I see no reason why
> a sane compiler for a silent-wraparound two's-complement machine should
> require that cast, but I wouldn't trust gcc to produce usable code without
> it, since it does not consistently produce arithmetically-correct code on
> silent-wraparound two's-complement platforms with 32-bit int and 16-bit
> short, for
>
> unsigned mul(unsigned short a, unsigned short b)
> { return a*b; }
>
> even though the authors of the Standard indicate in the rationale that
> they would expect such implementations to treat the multiply as being
> equivalent to unsigned.
>
The rules of C are clear here. If an "int" can represent all the values
of an "unsigned short", then a and b are promoted to "int" before the
multiplication (this is the case for 16-bit short and 32-bit int).
Otherwise, the operation will be done as unsigned int.
gcc follows those rules.
You might disagree with the choice of rules here, but that's the way C
works. The authors of the Standard indicate clearly that this is the
case, because it is written in the Standards. It is not even difficult
to find - it is right there in 6.3.1.1 at the start of the chapter on
"Conversions".
I suspect that what you are trying to say - yet again - is that there is
a possibility of undefined behaviour in a function like this, and that
possibility shocks you (and may be surprising to other people). And you
think the evil folks at gcc are waiting desperately for the chance to
catch someone out here and generate nasal daemons when they fall into
that trap.
The answer is, as always - write code that does not have such undefined
behaviour. And if you want your code to be portable, avoid
implementation defined behaviour (at least in relation to the targets
you expect to support).
And if you think your code cannot be written in a way that avoids
undefined behaviour, then you are probably using another of your
unrealistic hypothetical examples.
[toc] | [prev] | [next] | [standalone]
| From | Ben Bacarisse <ben.usenet@bsb.me.uk> |
|---|---|
| Date | 2016-08-09 11:23 +0100 |
| Message-ID | <87k2fqgs0e.fsf@bsb.me.uk> |
| In reply to | #92005 |
David Brown <david.brown@hesbynett.no> writes:
> On 08/08/16 23:09, supercat@casperkitty.com wrote:
>> On Monday, August 8, 2016 at 12:05:44 PM UTC-5, David Brown wrote:
>>> If you don't want implementation dependent behaviour in your programs,
>>> don't write code that depends on the implementation.
>>>
>>> int diffcomp(uint16_t a, uint16_b, uint16_t c) {
>>> return (unsigned int) a - b > c;
>>> }
>>
>> The effect of that will still vary depending upon the implementation.
>
> Perhaps I have got the details wrong, but my understanding here is:
>
> uint16_t is either "unsigned short", "unsigned int", or an unsigned
> extended integer type of lower rank than "unsigned int".
>
> "a" is cast to an unsigned int. This either involves no change at all,
> or a safe conversion since the range of unsigned int is at least as big
> as that of uint16_t.
>
> Before the "-" operation, the two operands need to be the same type.
> Since "unsigned int" has higher or equal rank to "uint16_t", and both
> are unsigned types, b is converted/promoted to "unsigned int".
>
> The "-" operation is carried out with unsigned int arithmetic (modular
> overflow handling).
>
> Similarly, "c" is converted/promoted as necessary to unsigned int, and
> the comparison is done with unsigned int arithmetic.
>
> The result of the comparison is an int, with value 0 or 1.
>
> The legal values for the arguments to the function are all 0..65535, as
> that is how uint16_t is defined.
>
> I see no scope for implementation-dependent behaviour here. But if you
> or anyone else sees something that I've missed, please say.
Take, for example, a=0, b=1, c=65535. On a machine with a 32-bit int
your code gives 1, but if the machine has a 16 bit int the result will
be 0.
>> Handling this particular expression in portable fashion requires casting
>> the arithmetic result, e.g.
>>
>> int diffcomp(uint16_t a, uint16_b, uint16_t c) {
>> return (uint16_t)(a - b) > c;
>
> That would work too, as far as I can see.
This version is just the zero function. Handling this in a portable
fashion really means writing
int diffcomp(uint16_t a, uint16_b, uint16_t c) { return 0; }
or, more likely, throwing the function away.
<snip>
--
Ben.
[toc] | [prev] | [next] | [standalone]
| From | Tim Rentsch <txr@alumni.caltech.edu> |
|---|---|
| Date | 2016-08-12 07:42 -0700 |
| Message-ID | <kfn37magib5.fsf@x-alumni2.alumni.caltech.edu> |
| In reply to | #92009 |
Ben Bacarisse <ben.usenet@bsb.me.uk> writes:
> David Brown <david.brown@hesbynett.no> writes:
>
>> On 08/08/16 23:09, supercat@casperkitty.com wrote:
>>> [...]
>>> Handling this particular expression in portable fashion requires
>>> casting the arithmetic result, e.g.
>>>
>>> int diffcomp(uint16_t a, uint16_b, uint16_t c) {
>>> return (uint16_t)(a - b) > c;
>>
>> That would work too, as far as I can see.
>
> This version is just the zero function. [...]
I don't see how you arrive at this conclusion. Assuming the
second parameter is supposed to be 'uint16_t b', it looks to me
like the return value depends on the values of a, b, and c, and
will be zero for some combination of argument values and one for
others. (I believe it also gives the same results for all
implementations that provide uint16_t, but I haven't looked at
that carefully.)
[toc] | [prev] | [next] | [standalone]
| From | Ben Bacarisse <ben.usenet@bsb.me.uk> |
|---|---|
| Date | 2016-08-12 21:38 +0100 |
| Message-ID | <87fuq9bu44.fsf@bsb.me.uk> |
| In reply to | #92192 |
Tim Rentsch <txr@alumni.caltech.edu> writes:
> Ben Bacarisse <ben.usenet@bsb.me.uk> writes:
>
>> David Brown <david.brown@hesbynett.no> writes:
>>
>>> On 08/08/16 23:09, supercat@casperkitty.com wrote:
>>>> [...]
>>>> Handling this particular expression in portable fashion requires
>>>> casting the arithmetic result, e.g.
>>>>
>>>> int diffcomp(uint16_t a, uint16_b, uint16_t c) {
>>>> return (uint16_t)(a - b) > c;
>>>
>>> That would work too, as far as I can see.
>>
>> This version is just the zero function. [...]
>
> I don't see how you arrive at this conclusion.
Indeed. I was assuming fixed values and variable implementations! I'd
picked values for a, b and c that gave, depending on the implementation,
different 0/1 results for the other example (now snipped). For this
example, those values always give zero no matter what the
implementation, but that's not at all what I wrote. (And the error was
not a typing error it was a total lack of brain function.)
<snip>
--
Ben.
[toc] | [prev] | [next] | [standalone]
| From | Keith Thompson <kst-u@mib.org> |
|---|---|
| Date | 2016-08-09 08:12 -0700 |
| Message-ID | <ln60raugbs.fsf@kst-u.example.com> |
| In reply to | #92005 |
David Brown <david.brown@hesbynett.no> writes:
> On 08/08/16 23:09, supercat@casperkitty.com wrote:
>> On Monday, August 8, 2016 at 12:05:44 PM UTC-5, David Brown wrote:
>>> If you don't want implementation dependent behaviour in your programs,
>>> don't write code that depends on the implementation.
>>>
>>> int diffcomp(uint16_t a, uint16_b, uint16_t c) {
>>> return (unsigned int) a - b > c;
>>> }
>>
>> The effect of that will still vary depending upon the implementation.
>
> Perhaps I have got the details wrong, but my understanding here is:
>
> uint16_t is either "unsigned short", "unsigned int", or an unsigned
> extended integer type of lower rank than "unsigned int".
It could also be unsigned char if CHAR_BIT==16. That doesn't affect the
rest of the discussion.
[...]
> But your example is an extremely hypothetical one. First, 64-bit int
> systems are rare - the only one I have heard of is some early Cray
> systems, and they also had 64-bit short (and therefore no uint32_t).
> Secondly, /if/ you were writing such modular arithmetic routines that
> had to be portable even to the Cray, you would use separate functions
> rather than casting. You would probably also not rely on the existence
> of uint32_t, but rather use "unsigned long" and explicit modulos for
> greater portability.
Cray vector systems such as the T90 and the more recent SV1 have
64-bit short, int, and long. Some other Cray systems, such as
the Alpha-based T3E, have 32-bit short and 64-bit int and long.
When I worked on them, there was no C99 compiler, and I don't *think*
there was a <stdint.h> or <inttypes.h> header.
Systems with 64-bit int are rare today, largely because making int 64
bits means you can't have 8-bit, 16-bit, 32-bit, and 64-bit integer
types without using extended integer types, and probably because the
size of int is imposed by the ABI. But I wouldn't assume that will
continue to be the case.
[...]
--
Keith Thompson (The_Other_Keith) kst-u@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
[toc] | [prev] | [next] | [standalone]
| From | supercat@casperkitty.com |
|---|---|
| Date | 2016-08-10 10:36 -0700 |
| Message-ID | <4dd0dbcf-53cc-476a-bd6c-f57425d08ae2@googlegroups.com> |
| In reply to | #92005 |
On Tuesday, August 9, 2016 at 2:38:28 AM UTC-5, David Brown wrote:
> uint16_t is either "unsigned short", "unsigned int", or an unsigned
> extended integer type of lower rank than "unsigned int".
Is there any prohibition of extended integer types whose rank is between
that of "int" and "long" (a lot of code for 8-bit micros could benefit from
having a 24-bit type, for example)? If not, would there be any
prohibition against int16_t and uint16_t ranking above "int" and "unsigned"?
While such behavior would be surprising for types with those names, there
are usage cases where it would be helpful to have 16-bit types that get
promoted to a longer type when acted upon (e.g. for holding counts whose
maximum value is 65,535, but which should behave like numbers when
subtracted).
[supercat:]
>> int diffcomp(uint16_t a, uint16_b, uint16_t c) {
>> return (unsigned int) a - b > c;
> "a" is cast to an unsigned int. This either involves no change at all,
> or a safe conversion since the range of unsigned int is at least as big
> as that of uint16_t.
>
> Before the "-" operation, the two operands need to be the same type.
> Since "unsigned int" has higher or equal rank to "uint16_t", and both
> are unsigned types, b is converted/promoted to "unsigned int".
>
> The "-" operation is carried out with unsigned int arithmetic (modular
> overflow handling).
> I see no scope for implementation-dependent behaviour here. But if you
> or anyone else sees something that I've missed, please say.
On systems where "int" is 16 bits, that is what happens. On systems where
"int" is 32 bits, the values would get promoted to "signed int", thus
changing the behavior of the subtraction from using modular arithmetic to
using "mathematical-integer" arithmetic.
> > Handling this particular expression in portable fashion requires casting
> > the arithmetic result, e.g.
> >
> > int diffcomp(uint16_t a, uint16_b, uint16_t c) {
> > return (uint16_t)(a - b) > c;
>
> That would work too, as far as I can see.
That's how I'd write the code, but code which relies upon modular
arithmetic without the cast exists in production, and will work unless
or until it's ported to a system where "int" is 32 bits or larger--thus
my point about the types not really making code as system-indendependent
as they it appears.
> > but some expressions may also require coercing intermediate values, e.g.
>
> That is true (though I think you mean "cast", rather than "coerce") -
> but you are now talking about a different function and a different
> expression. Different functions require different code to be correct -
> this should not really surprise anyone.
The purpose of the multiplication by 1u is to ensure that the value is
converted to an unsigned type without a cast (on systems where "unsigned
int" is 32 bits or larger, the passed-in values need to be converted to
with that type, but on systems where it's smaller than 32 bits, the
passed-in values must not be converted); the term "coerced" is used to
describe type conversions which are performed without casts.
> > int mod4294967296mulcomp(uint32_t a, uint32_t b, uint32_t c)
> > { return (uint32_t)(1u*a*b) > c; }
> >
> > The cast is necessary to ensure the value to be compared is reduced mod
> > 4294967296, but the before that can occur it will be necessary on 64-bit
> > systems to ensure that the result gets coerced to an unsigned type.
>
> On a system with 16-bit int, everything here will be done in unsigned
> longs, and no conversions or casts are needed.
>
> On a system with 32-bit int, uint32_t types might be automatically
> converted to unsigned int (if they were unsigned shorts, or an extended
> integer type), or they may remain as unsigned long (if that's the real
> type of uint32_t). Either way, no conversions or casts are needed as
> themultiplications are already done as modulo arithmetic.
>
> On a system with 64-bit int and support for uint32_t, then yes - a cast
> or an explicit & or % operation will be needed to get the effect you are
> asking for here.
On systems with 64-bit "int", failing to coerce the factors to an unsigned
type before doing the multiplication would entitle the compiler to behave
in arbitrary and wacky fashion if the integer product of the two factors
exceeds 2^63-1, despite the fact that the upper 32 bits of the result would
be ignored. I don't think the authors of the Standard intended that any
quality implementations for two's-complement silent-wraparound hardware
would use int overflow as an excuse for wacky behavior in scenarios where
there was no defined difference between signed and unsigned arithmetic, but
gcc sometimes does so anyway.
> But your example is an extremely hypothetical one. First, 64-bit int
> systems are rare - the only one I have heard of is some early Cray
> systems, and they also had 64-bit short (and therefore no uint32_t).
> Secondly, /if/ you were writing such modular arithmetic routines that
> had to be portable even to the Cray, you would use separate functions
> rather than casting. You would probably also not rely on the existence
> of uint32_t, but rather use "unsigned long" and explicit modulos for
> greater portability.
It is only the possibility of 64-bit "int" that would necessitate breaking
the decades-old convention that whenever practical implementations should
define:
char -- 8 bits
short -- 16 bits
int -- 16 or 32 bits as convenient
long -- 32 bits
long long, when available -- 64 bits
> I suspect that what you are trying to say - yet again - is that there is
> a possibility of undefined behaviour in a function like this, and that
> possibility shocks you (and may be surprising to other people). And you
> think the evil folks at gcc are waiting desperately for the chance to
> catch someone out here and generate nasal daemons when they fall into
> that trap.
Any useful implementation must offer more defined behaviors than mandated
by the Standard; the extent to which quality implementations should go
beyond the Standard will vary depending upon the target platform. A quality
implementation for a target with 512 bytes of data RAM might reasonably
limit the size of a "malloc" to 254 bytes, but any quality implementation
for a system with 16GB of RAM should be able to handle larger allocations.
Nothing in the Standard, however, would forbid a malloc implementation
which simply launched missiles unconditionally (follows from the fact that
implementations are not required to provide a means via which a program can
avoid crashing if code tries to allocates more space than is available, and
there is no guaranteed minimum amount of space available for allocation).
> The answer is, as always - write code that does not have such undefined
> behaviour. And if you want your code to be portable, avoid
> implementation defined behaviour (at least in relation to the targets
> you expect to support).
The determination between whether a behavior should be characterized as
IDB vs UB was based upon whether the authors of the Standard thought that
implementations might exist where the consequences could not be precisely
predicted. On some hardware platforms the effects of overflow may be
unpredictable, and characterizing it as IDB would require implementations
targeting such platforms to add extra code to make the effects predictable;
the authors of the Standard quite reasonably declined to do that. That
does not mean, however, that they intended that programmers should have
to write extra code to accommodate the quirks of unusual architectures
upon which their code would never be run.
> And if you think your code cannot be written in a way that avoids
> undefined behaviour, then you are probably using another of your
> unrealistic hypothetical examples.
In 1989, would anyone writing code which would only need to run on modern
microprocessors have had any reason to expect that any decent-quality
compiler for any such system would not regard:
unsigned mul(unsigned char x, unsigned char y) { return x*y; }
as equivalent to
unsigned mul(unsigned char x, unsigned char y) { return (unsigned)x*y; }
Since the C89 rationale suggests that the authors expected commonplace
platforms to consider them equivalent even in cases where the result was
in the range INT_MAX+1u to UINT_MAX, I'm curious why a programmer who was
targeting such platforms should have been expected to do otherwise.
Overflow behavior can be worked around, but at some expense in terms of
readability; working around unconstrained overflow behavior may also require
forcing the compiler to generate less-efficient code than would have been
possible if the programmer could exploit loose behavioral guarantees (e.g.
if the value of (x+y) will be within the range of "int" in all situations
where the programmer would about whether the expression (x+y)>z, allowing
the compiler to treat an overflowed value as being arbitrarily high or low
may enable optimizations that would be precluded if the programmer had to
write the expression as either x+(long long)y>z or (int)(x+(unsigned)y)>z.
[toc] | [prev] | [next] | [standalone]
Page 1 of 3 [1] 2 3 Next page →
Back to top | Article view | comp.lang.c
csiph-web