Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.c > #167553 > unrolled thread
| Started by | ManyBeers <markzuffi@yahoo.com> |
|---|---|
| First post | 2022-09-08 11:34 -0700 |
| Last post | 2022-09-13 15:44 -0700 |
| Articles | 20 on this page of 95 — 15 participants |
Back to article view | Back to comp.lang.c
Beginner....Decimal/Octal converter help ManyBeers <markzuffi@yahoo.com> - 2022-09-08 11:34 -0700
Re: Beginner....Decimal/Octal converter help Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2022-09-08 11:56 -0700
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-08 20:19 +0100
Re: Beginner....Decimal/Octal converter help Barry Schwarz <schwarzb@delq.com> - 2022-09-08 12:34 -0700
Re: Beginner....Decimal/Octal converter help Joe Pfeiffer <pfeiffer@cs.nmsu.edu> - 2022-09-08 20:38 -0600
Re: Beginner....Decimal/Octal converter help Öö Tiib <ootiib@hot.ee> - 2022-09-09 03:22 -0700
Re: Beginner....Decimal/Octal converter help gazelle@shell.xmission.com (Kenny McCormack) - 2022-09-09 13:30 +0000
Re: Beginner....Decimal/Octal converter help Tim Rentsch <tr.17687@z991.linuxsc.com> - 2022-09-09 09:16 -0700
Re: Beginner....Decimal/Octal converter help scott@slp53.sl.home (Scott Lurndal) - 2022-09-09 19:06 +0000
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-10 11:58 +0200
Re: Beginner....Decimal/Octal converter help Bart <bc@freeuk.com> - 2022-09-10 11:37 +0100
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-10 13:05 +0200
Re: Beginner....Decimal/Octal converter help scott@slp53.sl.home (Scott Lurndal) - 2022-09-10 14:42 +0000
Re: Beginner....Decimal/Octal converter help Bart <bc@freeuk.com> - 2022-09-10 16:10 +0100
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-10 18:14 +0200
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-10 12:54 +0100
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-10 16:22 +0200
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-10 19:03 +0100
Re: Beginner....Decimal/Octal converter help Bart <bc@freeuk.com> - 2022-09-10 19:30 +0100
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-10 19:47 +0100
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-11 14:07 +0200
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-11 16:12 +0100
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-11 17:58 +0200
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-11 21:14 +0100
Re: Beginner....Decimal/Octal converter help Tim Rentsch <tr.17687@z991.linuxsc.com> - 2022-09-11 20:01 -0700
Re: Beginner....Decimal/Octal converter help Richard Damon <Richard@Damon-Family.org> - 2022-09-11 23:32 -0400
Re: Beginner....Decimal/Octal converter help scott@slp53.sl.home (Scott Lurndal) - 2022-09-12 13:52 +0000
Re: Beginner....Decimal/Octal converter help Tim Rentsch <tr.17687@z991.linuxsc.com> - 2022-09-14 03:24 -0700
Re: Beginner....Decimal/Octal converter help Richard Damon <Richard@Damon-Family.org> - 2022-09-14 08:10 -0400
Re: Beginner....Decimal/Octal converter help Tim Rentsch <tr.17687@z991.linuxsc.com> - 2022-12-26 06:09 -0800
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-12 16:53 +0200
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-12 16:48 +0100
Re: Beginner....Decimal/Octal converter help Bart <bc@freeuk.com> - 2022-09-12 17:46 +0100
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-12 20:37 +0100
Re: Beginner....Decimal/Octal converter help Bart <bc@freeuk.com> - 2022-09-13 00:03 +0100
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-13 12:31 +0200
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-13 12:48 +0100
Re: Beginner....Decimal/Octal converter help Bart <bc@freeuk.com> - 2022-09-13 14:18 +0100
Re: Beginner....Decimal/Octal converter help Anton Shepelev <anton.txt@g{oogle}mail.com> - 2022-09-13 17:13 +0300
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-13 16:50 +0100
Re: Beginner....Decimal/Octal converter help Bart <bc@freeuk.com> - 2022-09-13 18:47 +0100
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-14 03:16 +0100
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-13 20:15 +0200
Re: Beginner....Decimal/Octal converter help Kaz Kylheku <480-992-1380@kylheku.com> - 2022-09-13 20:29 +0000
Re: Beginner....Decimal/Octal converter help Bart <bc@freeuk.com> - 2022-09-13 22:22 +0100
Re: Beginner....Decimal/Octal converter help Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2022-09-13 14:37 -0700
Re: Beginner....Decimal/Octal converter help Bart <bc@freeuk.com> - 2022-09-13 23:16 +0100
Re: Beginner....Decimal/Octal converter help scott@slp53.sl.home (Scott Lurndal) - 2022-09-13 22:16 +0000
Re: Beginner....Decimal/Octal converter help Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2022-09-13 16:15 -0700
Re: Beginner....Decimal/Octal converter help Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2022-09-13 16:15 -0700
Re: Beginner....Decimal/Octal converter help Bart <bc@freeuk.com> - 2022-09-14 10:58 +0100
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-14 15:36 +0100
Re: Beginner....Decimal/Octal converter help Richard Harnden <richard.nospam@gmail.com> - 2022-09-14 21:53 +0100
Re: Beginner....Decimal/Octal converter help Kaz Kylheku <480-992-1380@kylheku.com> - 2022-09-14 05:55 +0000
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-14 10:10 +0200
Re: Beginner....Decimal/Octal converter help Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2022-09-14 07:54 -0700
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-14 18:32 +0200
Re: Beginner....Decimal/Octal converter help Bart <bc@freeuk.com> - 2022-09-14 18:39 +0100
Re: Beginner....Decimal/Octal converter help Kaz Kylheku <480-992-1380@kylheku.com> - 2022-09-15 01:12 +0000
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-15 09:11 +0200
Re: Beginner....Decimal/Octal converter help Tim Rentsch <tr.17687@z991.linuxsc.com> - 2022-09-15 08:07 -0700
Re: Beginner....Decimal/Octal converter help Kaz Kylheku <480-992-1380@kylheku.com> - 2022-09-14 18:40 +0000
Re: Beginner....Decimal/Octal converter help Kaz Kylheku <480-992-1380@kylheku.com> - 2022-09-14 05:45 +0000
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-14 12:11 +0100
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-14 09:57 +0200
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-14 03:09 +0100
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-14 10:29 +0200
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-14 15:14 +0100
Re: Beginner....Decimal/Octal converter help Tim Rentsch <tr.17687@z991.linuxsc.com> - 2022-09-14 07:36 -0700
Re: Beginner....Decimal/Octal converter help Öö Tiib <ootiib@hot.ee> - 2022-10-03 01:10 -0700
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-10-03 11:36 +0100
Re: Beginner....Decimal/Octal converter help Öö Tiib <ootiib@hot.ee> - 2022-10-04 02:13 -0700
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-10-04 16:27 +0100
Re: Beginner....Decimal/Octal converter help Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2022-09-13 11:48 -0700
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-14 10:38 +0200
Re: Beginner....Decimal/Octal converter help scott@slp53.sl.home (Scott Lurndal) - 2022-09-14 14:27 +0000
Re: Beginner....Decimal/Octal converter help Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2022-09-14 07:58 -0700
Re: Beginner....Decimal/Octal converter help Tim Rentsch <tr.17687@z991.linuxsc.com> - 2022-09-16 13:06 -0700
Re: Beginner....Decimal/Octal converter help Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2022-09-16 13:16 -0700
Re: Beginner....Decimal/Octal converter help scott@slp53.sl.home (Scott Lurndal) - 2022-09-12 17:55 +0000
Re: Beginner....Decimal/Octal converter help Kaz Kylheku <480-992-1380@kylheku.com> - 2022-09-12 18:22 +0000
Re: Beginner....Decimal/Octal converter help Ben Bacarisse <ben.usenet@bsb.me.uk> - 2022-09-12 20:41 +0100
Re: Beginner....Decimal/Octal converter help Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2022-09-11 15:45 -0700
Re: Beginner....Decimal/Octal converter help Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2022-09-11 15:41 -0700
Re: Beginner....Decimal/Octal converter help David Brown <david.brown@hesbynett.no> - 2022-09-12 17:06 +0200
Re: Beginner....Decimal/Octal converter help Tim Rentsch <tr.17687@z991.linuxsc.com> - 2022-09-11 06:46 -0700
Re: Beginner....Decimal/Octal converter help Tim Rentsch <tr.17687@z991.linuxsc.com> - 2022-09-11 06:57 -0700
Re: Beginner....Decimal/Octal converter help Tim Rentsch <tr.17687@z991.linuxsc.com> - 2022-09-11 07:07 -0700
Neologism (Was: Beginner....Decimal/Octal converter help) gazelle@shell.xmission.com (Kenny McCormack) - 2022-09-11 14:18 +0000
Re: Beginner....Decimal/Octal converter help scott@slp53.sl.home (Scott Lurndal) - 2022-09-11 16:33 +0000
Re: Beginner....Decimal/Octal converter help Tim Rentsch <tr.17687@z991.linuxsc.com> - 2022-09-13 15:40 -0700
Re: Beginner....Decimal/Octal converter help scott@slp53.sl.home (Scott Lurndal) - 2022-09-09 23:08 +0000
Re: Beginner....Decimal/Octal converter help Tim Rentsch <tr.17687@z991.linuxsc.com> - 2022-09-11 06:37 -0700
Re: Beginner....Decimal/Octal converter help scott@slp53.sl.home (Scott Lurndal) - 2022-09-11 16:31 +0000
Re: Beginner....Decimal/Octal converter help Tim Rentsch <tr.17687@z991.linuxsc.com> - 2022-09-13 15:44 -0700
Page 2 of 5 — ← Prev page 1 [2] 3 4 5 Next page →
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2022-09-11 14:07 +0200 |
| Message-ID | <tfkj2l$1t7f3$1@dont-email.me> |
| In reply to | #167588 |
On 10/09/2022 20:30, Bart wrote: > On 10/09/2022 19:03, Ben Bacarisse wrote: >> David Brown <david.brown@hesbynett.no> writes: >> >>> On 10/09/2022 13:54, Ben Bacarisse wrote: >>>> David Brown <david.brown@hesbynett.no> writes: >>>> >>>>> On 09/09/2022 21:06, Scott Lurndal wrote: >>>>>> Tim Rentsch <tr.17687@z991.linuxsc.com> writes: >>>>>>> ManyBeers <markzuffi@yahoo.com> writes: >>>>>>> >>>>>>>> Hello I am teaching myself C and I need some help with a problem. >>>>>>>> After a search for dec/oct convert I found this code: >>>>>>>> >>>>>>>> [..code..] >>>>>>> >>>>>>> I would like to offer a different kind of answer to your >>>>>>> questions. First a suggestion: for this kind of problem I think >>>>>>> you will find it helpful to use unsigned types exclusively. In >>>>>>> the code below we will need (besides a plain 'unsigned' here and >>>>>>> there) two types, one for 8-bit values and one for 32-bit values: >>>>>>> >>>>>>> typedef unsigned char UC; >>>>>>> typedef unsigned int UI; >>>>>> There already standard perfectly cromulent typedefs for both, >>>>>> uint8_t and uint32_t. >>>>> >>>>> And those type names are better in a number of ways. They are >>>>> clearer, more precise, and hide the design flaw in C of mixing the >>>>> concepts of "character" and "small number". In particular, they >>>>> specify exactly what size your numbers are, rather that having hidden >>>>> assumptions in the code. >>>> But they both say too much. The 'source' integer size need not be 32 >>>> bits and, given that there are no other obvious requirements, unsigned >>>> int seems to me to be the natural choice. And if the requirements were >>>> that 32-bit numbers could be handled, uint_least32_t (or uint_fast32_t) >>>> more precisely meets the need. >>> >>> "unsigned int" might have been a reasonable choice, but Tim's code >>> goes on to rely on it being 32-bit. (Or to be more accurate, it >>> /says/ it relies on it being 32-bit.) >>> >>>> And the uint8_t is also unnecessarily specific. We do want to know >>>> that >>>> digits up to 255 can be stored, but nothing goes wrong if the type is >>>> wider. uint_least8_t says exactly what's wanted and no more. >>>> Mind you, since char is guaranteed to be at least 8 bits wide, unsigned >>>> char is effectively uint_least8_t. >>>> >>> >>> There is such a thing as being /too/ portable. >> >> I was not talking about portability. It's handy when types say what's >> needed and what isn't. uint8_t says too much. uint_least8_t is better >> because it says exactly what's needed and what isn't. The suggestion >> had nothing to do with portability. > > Surely uint_least8_t specifies even more? > > That's not a type I've come across anywhere else. And I'd be interested > in which machine it would map to anything other than 'unsigned char' (as > it does on x64. > > I can sort of see the use-case, but it sounds niche: you need a u8 type > but don't want to commit to exactly u8 because on some unusual > architecture you don't want it to jump through hoops to give you u8, > when u16 would do just as well. But specifying u16 anyway would be > wasteful everywhere else. > > And then there's uint_fast8_t; I can't even make a guess at that. All I > knows is that the last thing I want to see in source code is > uint_least8_t and uint_fast8_t (maybe 'const' too!) cluttering things up > and obscuring the underlying code. uint8_t is bad enough. > > I want to see something like 'byte' or 'u8'. For the unusual situation > above, deal with that in usercode via a special usertype. The "fast" and "least" types /do/ have their uses. There are not many targets that don't support 8-bit char and everything else as a power of 8 bits, but there /are/ a few. The main class of exceptions are DSP processors, and some highly specialised task-specific embedded processors. With C23, I believe types (u)int8_t, (u)int16_t, (u)int32_t and (u)int64_t have become mandatory - a target that does not support these natively must simulate them. This renders the "least" types obsolete. The "fast" types, on the other hand, can have real use in embedded systems that span a range of target devices. Some 16-bit, 32-bit and 64-bit processors require extra masking or extending instructions when dealing with 8-bit or 16-bit data (the original Alpha processor was particularly bad). So local variables of a larger type are more efficient. On the other hand, these will be much less efficient if the code is then compiled for an 8-bit target. The main type of interest here is "(u)int_fast8_t", since most uses of "int_fast16_t" can be written as "int".
[toc] | [prev] | [next] | [standalone]
| From | Ben Bacarisse <ben.usenet@bsb.me.uk> |
|---|---|
| Date | 2022-09-11 16:12 +0100 |
| Message-ID | <8735cy0wt4.fsf@bsb.me.uk> |
| In reply to | #167598 |
David Brown <david.brown@hesbynett.no> writes: > The "fast" and "least" types /do/ have their uses. There are not many > targets that don't support 8-bit char and everything else as a power > of 8 bits, but there /are/ a few. The main class of exceptions are > DSP processors, and some highly specialised task-specific embedded > processors. With C23, I believe types (u)int8_t, (u)int16_t, > (u)int32_t and (u)int64_t have become mandatory - a target that does > not support these natively must simulate them. This renders the > "least" types obsolete. You are looking at them entirely from the point of view of portability, but their main use is in explaining what data the program needs. In the vast number of cases, writing int_least8_t (say) says exactly what's needed and using int8_t says something incorrect about the code. The (u)intN_t types are most commonly needed to match external structures. There is very little code that breaks if the type is wider. The more common exception being cases where N-bit modulo arithmetic is part of the algorithm, in which case uintN_t describes the precisely. > The "fast" types, on the other hand, can have real use in embedded > systems that span a range of target devices. Some 16-bit, 32-bit and > 64-bit processors require extra masking or extending instructions when > dealing with 8-bit or 16-bit data (the original Alpha processor was > particularly bad). So local variables of a larger type are more > efficient. On the other hand, these will be much less efficient if > the code is then compiled for an 8-bit target. The main type of > interest here is "(u)int_fast8_t", since most uses of "int_fast16_t" > can be written as "int". In fact may programmers are, in effect, using the "least" types because int really means int_least16_t and long means int_least32_t. -- Ben.
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2022-09-11 17:58 +0200 |
| Message-ID | <tfl0im$1vu1b$1@dont-email.me> |
| In reply to | #167605 |
On 11/09/2022 17:12, Ben Bacarisse wrote: > David Brown <david.brown@hesbynett.no> writes: > >> The "fast" and "least" types /do/ have their uses. There are not many >> targets that don't support 8-bit char and everything else as a power >> of 8 bits, but there /are/ a few. The main class of exceptions are >> DSP processors, and some highly specialised task-specific embedded >> processors. With C23, I believe types (u)int8_t, (u)int16_t, >> (u)int32_t and (u)int64_t have become mandatory - a target that does >> not support these natively must simulate them. This renders the >> "least" types obsolete. > > You are looking at them entirely from the point of view of portability, > but their main use is in explaining what data the program needs. In the > vast number of cases, writing int_least8_t (say) says exactly what's > needed and using int8_t says something incorrect about the code. > C does not have a way to express such requirements in detail. Languages like Ada and Pascal let you have range types - you can say you want an integer type that holds a number between 0 and 9 (as is the case here). C can't do that. (C++ could, if you want to make the right template classes.) So "uint_least8_t" does not say what we want from a type in this example. We do not need a type that can hold at least 8 bits - we need a type that can hold the range 0 to 9. For that matter, we have no reason to say we want the /smallest/ type that can hold 8 bits, and thus "uint_least8_t" is no better than "uint_fast8_t" or "uint8_t" - each specifies the importance of a particular factor (small size, speed, precisely given size) when in fact we can disregard them all here. So as I see it, in many cases (such as Tim's code), using "uint_least8_t" would be just as much over-specifying the type requirements as "uint8_t" does, while not giving the programmer or reader any useful additional information. > The (u)intN_t types are most commonly needed to match external > structures. There is very little code that breaks if the type is wider. Agreed. > The more common exception being cases where N-bit modulo arithmetic is > part of the algorithm, in which case uintN_t describes the precisely. Agreed. > >> The "fast" types, on the other hand, can have real use in embedded >> systems that span a range of target devices. Some 16-bit, 32-bit and >> 64-bit processors require extra masking or extending instructions when >> dealing with 8-bit or 16-bit data (the original Alpha processor was >> particularly bad). So local variables of a larger type are more >> efficient. On the other hand, these will be much less efficient if >> the code is then compiled for an 8-bit target. The main type of >> interest here is "(u)int_fast8_t", since most uses of "int_fast16_t" >> can be written as "int". > > In fact may programmers are, in effect, using the "least" types because > int really means int_least16_t and long means int_least32_t. > No, I would say "int" corresponds closer to "int_fast16_t", rather than "int_least16_t". Almost every 32-bit processor also supports 16-bit types, meaning "int_least16_t" would have to be 16 bits - yet their "int" will generally be 32-bit. Implementations have the flexibility to choose 32-bit for "int_fast16_t" on such processors. And "int_fast16_t" better matches the programmer's needs when he/she writes" int". Still, almost all 32-bit (or bigger) systems implement both int_fast16_t and int_least16_t as the same as int16_t, while having "int" as 32-bit.
[toc] | [prev] | [next] | [standalone]
| From | Ben Bacarisse <ben.usenet@bsb.me.uk> |
|---|---|
| Date | 2022-09-11 21:14 +0100 |
| Message-ID | <87r10hzn19.fsf@bsb.me.uk> |
| In reply to | #167608 |
David Brown <david.brown@hesbynett.no> writes: > On 11/09/2022 17:12, Ben Bacarisse wrote: >> David Brown <david.brown@hesbynett.no> writes: >> >>> The "fast" and "least" types /do/ have their uses. There are not many >>> targets that don't support 8-bit char and everything else as a power >>> of 8 bits, but there /are/ a few. The main class of exceptions are >>> DSP processors, and some highly specialised task-specific embedded >>> processors. With C23, I believe types (u)int8_t, (u)int16_t, >>> (u)int32_t and (u)int64_t have become mandatory - a target that does >>> not support these natively must simulate them. This renders the >>> "least" types obsolete. >> You are looking at them entirely from the point of view of portability, >> but their main use is in explaining what data the program needs. In the >> vast number of cases, writing int_least8_t (say) says exactly what's >> needed and using int8_t says something incorrect about the code. > > C does not have a way to express such requirements in detail. > Languages like Ada and Pascal let you have range types - you can say > you want an integer type that holds a number between 0 and 9 (as is > the case here). No, that was not the case. But anyway, the fact that you can't say "0 to 9" does not make "exactly 8 bits" better than "at least 8 bits". > So "uint_least8_t" does not say what we want from a type in this > example. The example is lost and was not, I think, limited in the way you've said. But I was explicitly not talking about one example. I said "In the vast number of cases...". By all means say I'm wrong, or say that you don't care to generalise, but countering a general remark with one specific case is not helpful. > So as I see it, in many cases (such as Tim's code), using > "uint_least8_t" would be just as much over-specifying the type > requirements as "uint8_t" does, while not giving the programmer or > reader any useful additional information. I disagree about this example, but I also don't want to get into "many". I miss-edited my test and ended up with "In the vast number of cases..." when I meant to say "In the vast majority of cases...". I hope the word vast made it clear that was not a quibble about numbers. I worry that, except when matching externally defined structures, the use of the intN_t (and maybe even the uintN_t) types is almost always knee-jerk programming. You don't see them a lot (even now), but I don't recall seeing any code that uses an intN_t type where int_leastN_t or int_fastN_t would not have been better (excluding the struct-matching cases). They are mandatory and don't imply the need for a particular representation (no padding and two's complement). My experience of C code post C99 is rather limited so I am open to persuasion here. <cut> >> In fact may programmers are, in effect, using the "least" types because >> int really means int_least16_t and long means int_least32_t. > > No, I would say "int" corresponds closer to "int_fast16_t", rather > than "int_least16_t". Yes, that's a closer match, though neither is exact. It's possible (though not likely) that int_fact16_t is 16 bits even when int is 32 bits, but int_least16_t is very likely to 16-bits in such cases as you say: > Almost every 32-bit processor also supports 16-bit types, meaning > "int_least16_t" would have to be 16 bits - yet their "int" will > generally be 32-bit. -- Ben.
[toc] | [prev] | [next] | [standalone]
| From | Tim Rentsch <tr.17687@z991.linuxsc.com> |
|---|---|
| Date | 2022-09-11 20:01 -0700 |
| Message-ID | <86v8ptpa7u.fsf@linuxsc.com> |
| In reply to | #167619 |
Ben Bacarisse <ben.usenet@bsb.me.uk> writes: > [...] I worry that, except when matching externally defined > structures, the use of the intN_t (and maybe even the uintN_t) > types is almost always knee-jerk programming. Completely agree.
[toc] | [prev] | [next] | [standalone]
| From | Richard Damon <Richard@Damon-Family.org> |
|---|---|
| Date | 2022-09-11 23:32 -0400 |
| Message-ID | <cLxTK.200646$PRW4.166956@fx11.iad> |
| In reply to | #167623 |
On 9/11/22 11:01 PM, Tim Rentsch wrote: > Ben Bacarisse <ben.usenet@bsb.me.uk> writes: > >> [...] I worry that, except when matching externally defined >> structures, the use of the intN_t (and maybe even the uintN_t) >> types is almost always knee-jerk programming. > > Completely agree. I would just slightly disagree. Many programs are not designed to be "highly portable", and it may be a reasonable assumption that a given program will only be run on machines which is based on 8 bit bytes. Since [u]intN_t is the shortest way to specify a size that will likely migrate over modereate architecture variations for variable where you do want to specify the "size" of the variable, using them makes sense. Perhaps instead of making the type least_intN_t/fast_intN_t/intN_t they were defined instead as intN_t/fast_intN_t/exact_intN_t, or least_intN_t/intN_t/exact_intN_t so the simple name was one of the promised to exist types that might be bigger if needed, the short name bias would have lead to better code, but that ship has sailed.
[toc] | [prev] | [next] | [standalone]
| From | scott@slp53.sl.home (Scott Lurndal) |
|---|---|
| Date | 2022-09-12 13:52 +0000 |
| Message-ID | <jQGTK.472156$BKL8.234684@fx15.iad> |
| In reply to | #167624 |
Richard Damon <Richard@Damon-Family.org> writes: >On 9/11/22 11:01 PM, Tim Rentsch wrote: >> Ben Bacarisse <ben.usenet@bsb.me.uk> writes: >> >>> [...] I worry that, except when matching externally defined >>> structures, the use of the intN_t (and maybe even the uintN_t) >>> types is almost always knee-jerk programming. >> >> Completely agree. > >I would just slightly disagree. > >Many programs are not designed to be "highly portable", and it may be a >reasonable assumption that a given program will only be run on machines >which is based on 8 bit bytes. I'd go so far as admitting that _most_ C programs are not designed to be highly portable. Certainly the code that I have written in C (operating systems, hypervisors, machine simulators, architectural exploration vehicles, libraries, etc) has been designed for specific hardware platforms - yet it is mostly portable anyway since pretty much every modern architecture has similar basic hardware data types. Take the Linux operating system, for example; a large fraction of the code compiles on dozens of different processor architectures (including harvard).
[toc] | [prev] | [next] | [standalone]
| From | Tim Rentsch <tr.17687@z991.linuxsc.com> |
|---|---|
| Date | 2022-09-14 03:24 -0700 |
| Message-ID | <867d26ntiv.fsf@linuxsc.com> |
| In reply to | #167624 |
Richard Damon <Richard@Damon-Family.org> writes:
> On 9/11/22 11:01 PM, Tim Rentsch wrote:
>
>> Ben Bacarisse <ben.usenet@bsb.me.uk> writes:
>>
>>> [...] I worry that, except when matching externally defined
>>> structures, the use of the intN_t (and maybe even the uintN_t)
>>> types is almost always knee-jerk programming.
>>
>> Completely agree.
>
> I would just slightly disagree.
>
> Many programs are not designed to be "highly portable", and it may be
> a reasonable assumption that a given program will only be run on
> machines which is based on 8 bit bytes.
I am taking 8-bit bytes as being the crucial property in the
remarks below. There are obvious counterparts for other
properties, such as having a two's complement representation.
> Since [u]intN_t is the shortest way to specify a size that will likely
> migrate over modereate architecture variations for variable where you
> do want to specify the "size" of the variable, using them makes sense.
First off, I wouldn't say using int8_t or uint8_t is the shortest
way to ensure 8-bit bytes. The way such types are typically
used, there will be lots of 'int8_t's or 'uint8_t's scattered
through the source code, with a correspondingly large footprint
in the program source. It is shorter to write in just one header
file in the program
#include <limits.h>
#if CHAR_BIT != 8
# error oops.. this program relies on chars being 8 bits...
#endif
typedef unsigned char U8;
typedef signed char S8;
which also can be used in implementations that don't support
C99.
Second, ignoring the question of which technique is shortest,
using [u]int8_t is often not a good way. A problem with the
exact width types is that they are both an under-specification and
an over-specification: over-specification because they often
include properties that aren't important (such as having no trap
representations), and under-specification because they might not
have properties that are otherwise desirable (such as being
exempt from anti-alias rules, as the character types are).
Granted, such problems are not likely to occur, but there is no
good reason to take the risk when it can be easily avoided.
Third, using exact width types can have consequences for parts of
the code other than declarations. In effect using exact width
types in one part of a program pushes the program into a mode
where exact width types more or less have to be used everywhere,
even when they are needed in only a few places. Even if exact
width types are an acceptable choice, in most cases they are not
the best choice.
In summary, I think a better statement is that [u]intN_t is the
laziest way to achieve what may not be a good result, and often
is a suboptimal result. The sad thing is that better techniques
don't need a lot of overhead; there is a small upfront cost, but
once that initial cost has been paid the incremental cost is
basically zero.
> Perhaps instead of making the type least_intN_t/fast_intN_t/intN_t
> they were defined instead as intN_t/fast_intN_t/exact_intN_t, or
> least_intN_t/intN_t/exact_intN_t so the simple name was one of the
> promised to exist types that might be bigger if needed, the short name
> bias would have lead to better code, but that ship has sailed.
Something that hasn't come up (I think) in the discussion of
various type choices is the distinction between types and the
names of types. In the code that I posted, the types used are
unsigned char and unsigned int. To help the discussion I put in
some typedefs to give the names UC and UI to these types, but
that doesn't change what the underlying types are. One problem
with the types in <stdint.h> is we don't know whether these names
refer to existing (standard) types, or whether they refer to
non-standard extended types. Are they fish or fowl? We don't
know. That uncertainty can cause difficulties in code that uses
some <stdint.h> types. For the most part I usually avoid using
any <stdint.h> types, for this reason and also for the reasons
explained above.
[toc] | [prev] | [next] | [standalone]
| From | Richard Damon <Richard@Damon-Family.org> |
|---|---|
| Date | 2022-09-14 08:10 -0400 |
| Message-ID | <uwjUK.126149$IRd5.25738@fx10.iad> |
| In reply to | #167704 |
On 9/14/22 6:24 AM, Tim Rentsch wrote: > Richard Damon <Richard@Damon-Family.org> writes: > >> On 9/11/22 11:01 PM, Tim Rentsch wrote: >> >>> Ben Bacarisse <ben.usenet@bsb.me.uk> writes: >>> >>>> [...] I worry that, except when matching externally defined >>>> structures, the use of the intN_t (and maybe even the uintN_t) >>>> types is almost always knee-jerk programming. >>> >>> Completely agree. >> >> I would just slightly disagree. >> >> Many programs are not designed to be "highly portable", and it may be >> a reasonable assumption that a given program will only be run on >> machines which is based on 8 bit bytes. > > I am taking 8-bit bytes as being the crucial property in the > remarks below. There are obvious counterparts for other > properties, such as having a two's complement representation. > >> Since [u]intN_t is the shortest way to specify a size that will likely >> migrate over modereate architecture variations for variable where you >> do want to specify the "size" of the variable, using them makes sense. > > First off, I wouldn't say using int8_t or uint8_t is the shortest > way to ensure 8-bit bytes. The way such types are typically > used, there will be lots of 'int8_t's or 'uint8_t's scattered > through the source code, with a correspondingly large footprint > in the program source. It is shorter to write in just one header > file in the program > > #include <limits.h> > #if CHAR_BIT != 8 > # error oops.. this program relies on chars being 8 bits... > #endif > > typedef unsigned char U8; > typedef signed char S8; > > which also can be used in implementations that don't support > C99. Yes, but then you get the problem that stdint.h was made to solve, that of many variations of this naming possible existing in the program, and the risk of duplication of names. I suspect that one reason int8_t is slightly ugly is that it was a name the standard could fairly safely grab and not get into too many conflicts. Yes, a final application code could do something like that, and not have issues, but library code can't afford the name pollution. > > Second, ignoring the question of which technique is shortest, > using [u]int8_t is often not a good way. A problem with the > exact width types is that they are both an under-specification and > an over-specification: over-specification because they often > include properties that aren't important (such as having no trap > representations), and under-specification because they might not > have properties that are otherwise desirable (such as being > exempt from anti-alias rules, as the character types are). > Granted, such problems are not likely to occur, but there is no > good reason to take the risk when it can be easily avoided. Readability of simple STANDARD names is important. The looser specified types will have almost exactly properties. Remember, the criteria was that the programmer KNEW that the machine was a standard 8-bit byte two's complement machine due to other dependencies in the code, thus KNOWS that the "over-specified" properties WILL hold. As far as exempt from the anti-alias rules, I don't think there has been a "standard" byte sized type that doesn't have that exemption (I think they are adding one in the newest Standard, but that is a very new addition. Remember, int_least8_t is likely a typedef to signed char so still gets that attribute. > > Third, using exact width types can have consequences for parts of > the code other than declarations. In effect using exact width > types in one part of a program pushes the program into a mode > where exact width types more or less have to be used everywhere, > even when they are needed in only a few places. Even if exact > width types are an acceptable choice, in most cases they are not > the best choice. As I pointed out, this method is used when you can assume the architecture supports those types. Except for buffers and the like passed by pointers (which likely should be void* or the exact size likely IS important) since these types are just typedefs, calling a routine defined to take a int_least16_t can easily be called with a int16_t parameter with NO problem. > > In summary, I think a better statement is that [u]intN_t is the > laziest way to achieve what may not be a good result, and often > is a suboptimal result. The sad thing is that better techniques > don't need a lot of overhead; there is a small upfront cost, but > once that initial cost has been paid the incremental cost is > basically zero. That exact same arguement is what causes some people to do things like #define F for #define R return ... and get obfuscated code. The big issue with user type like u8 is that this becomes a risk in library code that needs to be able to move to other projects. I would disagree that they "often" result is "suboptimal" results, as it looks like you methods end up with EXACTLY the same fundamental types being uses in the normal case, so there as no suboptimal. Yes, if your goal is "maximum portability" to unusual architectures, you need to be a bit more careful. > >> Perhaps instead of making the type least_intN_t/fast_intN_t/intN_t >> they were defined instead as intN_t/fast_intN_t/exact_intN_t, or >> least_intN_t/intN_t/exact_intN_t so the simple name was one of the >> promised to exist types that might be bigger if needed, the short name >> bias would have lead to better code, but that ship has sailed. > > Something that hasn't come up (I think) in the discussion of > various type choices is the distinction between types and the > names of types. In the code that I posted, the types used are > unsigned char and unsigned int. To help the discussion I put in > some typedefs to give the names UC and UI to these types, but > that doesn't change what the underlying types are. One problem > with the types in <stdint.h> is we don't know whether these names > refer to existing (standard) types, or whether they refer to > non-standard extended types. Are they fish or fowl? We don't > know. That uncertainty can cause difficulties in code that uses > some <stdint.h> types. For the most part I usually avoid using > any <stdint.h> types, for this reason and also for the reasons > explained above. Does it matter if they are "standard" types of not? Again, my premise was that I KNEW my code was going to run on a "Standard" 8 bit byte, twos complement machine with all the normal assumptions, because other parts were something somewhat machine specific. Yes, if you are writting maximally compatible code that might end up on something like a DSP where that doesn't hold, don't do it that way.
[toc] | [prev] | [next] | [standalone]
| From | Tim Rentsch <tr.17687@z991.linuxsc.com> |
|---|---|
| Date | 2022-12-26 06:09 -0800 |
| Message-ID | <86tu1iw91p.fsf@linuxsc.com> |
| In reply to | #167706 |
Richard Damon <Richard@Damon-Family.org> writes: > On 9/14/22 6:24 AM, Tim Rentsch wrote: > >> Richard Damon <Richard@Damon-Family.org> writes: >> >>> On 9/11/22 11:01 PM, Tim Rentsch wrote: >>> >>>> Ben Bacarisse <ben.usenet@bsb.me.uk> writes: >>>> >>>>> [...] I worry that, except when matching externally defined >>>>> structures, the use of the intN_t (and maybe even the uintN_t) >>>>> types is almost always knee-jerk programming. >>>> >>>> Completely agree. >>> >>> I would just slightly disagree. >>> >>> Many programs are not designed to be "highly portable", and it may >>> be a reasonable assumption that a given program will only be run >>> on machines which is based on 8 bit bytes. >> >> I am taking 8-bit bytes as being the crucial property in the >> remarks below. There are obvious counterparts for other >> properties, such as having a two's complement representation. >> >>> Since [u]intN_t is the shortest way to specify a size that will >>> likely migrate over modereate architecture variations for variable >>> where you do want to specify the "size" of the variable, using >>> them makes sense. >> >> First off, I wouldn't say using int8_t or uint8_t is the shortest >> way to ensure 8-bit bytes. The way such types are typically >> used, there will be lots of 'int8_t's or 'uint8_t's scattered >> through the source code, with a correspondingly large footprint >> in the program source. It is shorter to write in just one header >> file in the program >> >> #include <limits.h> >> #if CHAR_BIT != 8 >> # error oops.. this program relies on chars being 8 bits... >> #endif >> >> typedef unsigned char U8; >> typedef signed char S8; >> >> which also can be used in implementations that don't support >> C99. > > Yes, but then you get the problem that stdint.h was made to solve, > that of many variations of this naming possible existing in the > program, and the risk of duplication of names. That's your imagination talking, not the C standard. > I suspect that one reason int8_t is slightly ugly is that it was a > name the standard could fairly safely grab and not get into too > many conflicts. > > Yes, a final application code could do something like that, and > not have issues, but library code can't afford the name pollution. The [u]intN_t types are not used elsewhere in the C library. (I believe that also holds true for the fast and least-width types.) Some people might imagine that they are, but they are not. >> Second, ignoring the question of which technique is shortest, >> using [u]int8_t is often not a good way. A problem with the >> exact width types is that they are both an under-specification and >> an over-specification: over-specification because they often >> include properties that aren't important (such as having no trap >> representations), and under-specification because they might not >> have properties that are otherwise desirable (such as being >> exempt from anti-alias rules, as the character types are). >> Granted, such problems are not likely to occur, but there is no >> good reason to take the risk when it can be easily avoided. > > Readability of simple STANDARD names is important. The looser > specified types will have almost exactly properties. Remember, > the criteria was that the programmer KNEW that the machine was a > standard 8-bit byte two's complement machine due to other > dependencies in the code, thus KNOWS that the "over-specified" > properties WILL hold. > > As far as exempt from the anti-alias rules, I don't think there > has been a "standard" byte sized type that doesn't have that > exemption (I think they are adding one in the newest Standard, but > that is a very new addition. Remember, int_least8_t is likely a > typedef to signed char so still gets that attribute. > >> Third, using exact width types can have consequences for parts of >> the code other than declarations. In effect using exact width >> types in one part of a program pushes the program into a mode >> where exact width types more or less have to be used everywhere, >> even when they are needed in only a few places. Even if exact >> width types are an acceptable choice, in most cases they are not >> the best choice. > > As I pointed out, this method is used when you can assume the > architecture supports those types. Except for buffers and the > like passed by pointers (which likely should be void* or the exact > size likely IS important) since these types are just typedefs, > calling a routine defined to take a int_least16_t can easily be > called with a int16_t parameter with NO problem. > >> In summary, I think a better statement is that [u]intN_t is the >> laziest way to achieve what may not be a good result, and often >> is a suboptimal result. The sad thing is that better techniques >> don't need a lot of overhead; there is a small upfront cost, but >> once that initial cost has been paid the incremental cost is >> basically zero. > > That exact same arguement is what causes some people to do things > like > > #define F for > #define R return > ... > and get obfuscated code. I don't think so. Similar arguments maybe, but certainly not exactly the same. > The big issue with user type like u8 is that this becomes a risk in > library code that needs to be able to move to other projects. > > I would disagree that they "often" result is "suboptimal" results, as > it looks like you methods end up with EXACTLY the same fundamental > types being uses in the normal case, so there as no suboptimal. > > Yes, if your goal is "maximum portability" to unusual architectures, > you need to be a bit more careful. > >>> Perhaps instead of making the type least_intN_t/fast_intN_t/intN_t >>> they were defined instead as intN_t/fast_intN_t/exact_intN_t, or >>> least_intN_t/intN_t/exact_intN_t so the simple name was one of the >>> promised to exist types that might be bigger if needed, the short name >>> bias would have lead to better code, but that ship has sailed. >> >> Something that hasn't come up (I think) in the discussion of >> various type choices is the distinction between types and the >> names of types. In the code that I posted, the types used are >> unsigned char and unsigned int. To help the discussion I put in >> some typedefs to give the names UC and UI to these types, but >> that doesn't change what the underlying types are. One problem >> with the types in <stdint.h> is we don't know whether these names >> refer to existing (standard) types, or whether they refer to >> non-standard extended types. Are they fish or fowl? We don't >> know. That uncertainty can cause difficulties in code that uses >> some <stdint.h> types. For the most part I usually avoid using >> any <stdint.h> types, for this reason and also for the reasons >> explained above. > > Does it matter if they are "standard" types of not? > > Again, my premise was that I KNEW my code was going to run on a > "Standard" 8 bit byte, twos complement machine with all the normal > assumptions, because other parts were something somewhat machine > specific. > > Yes, if you are writting maximally compatible code that might end up > on something like a DSP where that doesn't hold, don't do it that way. ISTM your argument boils down to, because a bunch of other people used the exact-width types wrongly, that I should also. Other people may find such an argument convincing, but I do not.
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2022-09-12 16:53 +0200 |
| Message-ID | <tfnh5k$29dcu$1@dont-email.me> |
| In reply to | #167619 |
On 11/09/2022 22:14, Ben Bacarisse wrote: > David Brown <david.brown@hesbynett.no> writes: > >> On 11/09/2022 17:12, Ben Bacarisse wrote: >>> David Brown <david.brown@hesbynett.no> writes: >>> >>>> The "fast" and "least" types /do/ have their uses. There are not many >>>> targets that don't support 8-bit char and everything else as a power >>>> of 8 bits, but there /are/ a few. The main class of exceptions are >>>> DSP processors, and some highly specialised task-specific embedded >>>> processors. With C23, I believe types (u)int8_t, (u)int16_t, >>>> (u)int32_t and (u)int64_t have become mandatory - a target that does >>>> not support these natively must simulate them. This renders the >>>> "least" types obsolete. >>> You are looking at them entirely from the point of view of portability, >>> but their main use is in explaining what data the program needs. In the >>> vast number of cases, writing int_least8_t (say) says exactly what's >>> needed and using int8_t says something incorrect about the code. >> >> C does not have a way to express such requirements in detail. >> Languages like Ada and Pascal let you have range types - you can say >> you want an integer type that holds a number between 0 and 9 (as is >> the case here). > > No, that was not the case. Sorry, it should be 0 to 15 the code also converts hex. But the same principle remains. > > But anyway, the fact that you can't say "0 to 9" does not make "exactly > 8 bits" better than "at least 8 bits". Equally, it makes "at least 8 bits but the smallest type fulfilling that" no better than "exactly 8 bits". (Nor is "at least 8 bits, with the fastest type you have" any better.) "uint8_t" is simpler and clearer than "uint_least8_t" (or "uint_fast8_t"). There is less ambiguity, greater familiarity, and more usefulness for the learner. In low-level C code, types like "uint8_t" and "uint32_t" turn up all the time - and project and library specific versions abound (as Bart constantly reminds us). The "least" and "fast" types are almost unused in practice. > >> So "uint_least8_t" does not say what we want from a type in this >> example. > > The example is lost and was not, I think, limited in the way you've > said. But I was explicitly not talking about one example. I said "In > the vast number of cases...". By all means say I'm wrong, or say that > you don't care to generalise, but countering a general remark with one > specific case is not helpful. Sorry, I was still considering the particular example. If we are generalising, we still need to have some restrictions to be able to say something useful - perhaps "types to hold small non-negative numbers" is appropriate. I agree with you that "uint_least8_t" and similar types could be technically better choices in a number of cases, but not all decisions are made for technical reasons. I don't agree that it is better in a "vast number" of cases - because it does /not/ say exactly what is needed. In almost all cases of "small number", the upper bound is /not/ 255. Thus "uint_least8_t" does not say exactly what you want - it is wrong in a different way from "uint8_t", but still wrong. I think to some extent it comes down to a subjective opinion of what we are willing to be wrong about and what we prefer to be correct about in how we code. There are always compromises. And we are both aware that trying to be general, and talking about "many cases" or "most situations", is always going to be somewhat unsubstantiated and subjective. All such comments are implicitly IME and IMHO. > >> So as I see it, in many cases (such as Tim's code), using >> "uint_least8_t" would be just as much over-specifying the type >> requirements as "uint8_t" does, while not giving the programmer or >> reader any useful additional information. > > I disagree about this example, but I also don't want to get into "many". > > I miss-edited my test and ended up with "In the vast number of cases..." > when I meant to say "In the vast majority of cases...". I hope the word > vast made it clear that was not a quibble about numbers. I worry that, > except when matching externally defined structures, the use of the > intN_t (and maybe even the uintN_t) types is almost always knee-jerk > programming. > I don't see it as knee-jerk programming - though I can appreciate your concern. (Maybe it /is/ knee-jerk programming for some people.) This is C programming. For a lot of C programming, people like to know exactly what they get. (Many people believe C gives them stronger guarantees that it really does in that respect, but that's another matter.) The fixed size types gives them that assurance. Are they tighter specified than usually required? Yes, typically - but that is generally the case in /all/ programming in /all/ languages. We are forever using types or functions that are more general than actually needed, and equally which have specific guaranteed features that we don't want to say because they are not relevant - few languages let you be that precise, and when they do, few programmers bother to use those features all the time. Does anyone complain about "for (int i = 0; i < 100000; i++)" or "for (int i = 0; i < 100; i++)" ? In the first case, "int" is underspecified as the type means "at least 16 bit". In the second, it is over specified as "at least 8 bit" would be closer to the truth of what we want. When coding, context is important for the way the programmer communicates to other programmers. Just as you can't properly understand prose taken out of context, the same applies to programming. If a programmer uses "uint32_t" well, it is usually clear whether the exact size is important, or merely the range. You can generally also tell if the wrapping overflow behaviour is important. It might be more precise to use "uint_wrapping32_t" and "uint_dontcareaboutwrapping32_t", but too much detail is impractical. However, different people will draw the line in different places. > You don't see them a lot (even now), but I don't recall seeing any code > that uses an intN_t type where int_leastN_t or int_fastN_t would not > have been better (excluding the struct-matching cases). They are > mandatory and don't imply the need for a particular representation (no > padding and two's complement). > Let's be realistic - all signed integers are stored as two's complement, and there are no padding bits except in bools. Exceptions are dinosaurs that are negligible in the context of modern C programming. While there is rarely any necessity or advantage in requiring that a variable be stored without padding bits, there is no advantage is specifying that you don't care about padding bits. And the types (u)int8_t, (u)int16_t, (u)int32_t and (u)int64_t are also mandatory in practice, baring very niche targets such as some DSPs, because the implementation must provide them if it has suitable types already - as virtually all implementations do. In C23, even that get-out clause is gone. Usage of different features of C vary according to task. In my field, the fixed sized types dominate for many programmers. Some style guides ban "indeterminate sized" types entirely - no "int" or "short". (I am not saying this is necessarily a good idea, merely that it is the case.) In general, you'll see fixed size types in a lot of low-level C programming. And in general, if the code is not low-level, there are probably more appropriate languages than C for the task (at least for new code). Newer languages targeting low level coding almost invariably have fixed-size types as their only primitive integer types - skipping the indecisiveness of C's "int", "short" and "long", and taking no interest in the "least" or "fast" types. If those types had significant usefulness or advantages, we'd see them in D, Rust, Golang, Swift, Java, Zig, etc. I really do not think the "least" or "fast" types give any real advantage in most cases (while accepting that they do so in /some/ cases). The extra programmer semantic information they carry is rarely useful, and often inaccurate (such as explicitly saying the type needs to have at least 16 bits, when in fact 12 bits would be enough). The programmer is left to choose arbitrarily between multiple slightly inaccurate types - leaving the next programmer to wonder why a particular choice was made. I am all in favour of using different types when appropriate, and in being explicit in my choices and in the names of types - but I don't approve of proliferating types that could all equally well be the same, without real reasons. It can lead to mixups, unexpected pointer incompatibilities (or unexpected compatibilities), programmer confusion, noise in static error checking, and unnecessarily verbose code. So while I can agree that the "least" and "fast" types might often be "better" in /some/ aspects, I do not agree that they are often "better" overall. > My experience of C code post C99 is rather limited so I am open to > persuasion here. > I don't know how persuasive I am, but I hope I have shown why I disagree with you (while fully accepting that opinions differ on such subjective matters). Note that pre-C99, "home-made" fixed size types abounded. Libraries and low-level code are full of types called "u32", "dword", "byte", "sqlite_int32", and so on. I cannot say I have /ever/ seen a home-made type matching a "least" or "fast" stdint.h type. > <cut> >>> In fact may programmers are, in effect, using the "least" types because >>> int really means int_least16_t and long means int_least32_t. >> >> No, I would say "int" corresponds closer to "int_fast16_t", rather >> than "int_least16_t". > > Yes, that's a closer match, though neither is exact. It's possible > (though not likely) that int_fact16_t is 16 bits even when int is 32 > bits, but int_least16_t is very likely to 16-bits in such cases as you > say: A quick check (on godbolt) suggests that on x86-64, "fast8" is 8-bit, while "fast16" upwards is 64-bit on *nix and 32-bit on MSVC and x86-32 for both platforms. It's the same pattern for mips, ppc, and arm. But there are some exceptions for other targets. I suspect many would be surprised to find that "int_fast16_t" is bigger than "int". Their code should not be affected by it, of course - if their code relies on such relative sizes then "int_fast16_t" is not the right type to use. But I think many C programmers feel happier knowing the size of their types, even if it is not actually important to their code. Having "int" defined by the standards as an implementation-dependent native type with a range of at least 16 bits that can be handled quickly and efficiently, and then "int_fast16_t" defined as an implementation-dependent type with a range of at least 16 bits that can be handled as quickly as possible, and then finding that these are frequently different and neither are 16-bit, is maybe not what people expect from the language. > >> Almost every 32-bit processor also supports 16-bit types, meaning >> "int_least16_t" would have to be 16 bits - yet their "int" will >> generally be 32-bit. >
[toc] | [prev] | [next] | [standalone]
| From | Ben Bacarisse <ben.usenet@bsb.me.uk> |
|---|---|
| Date | 2022-09-12 16:48 +0100 |
| Message-ID | <87mtb4wq4f.fsf@bsb.me.uk> |
| In reply to | #167641 |
David Brown <david.brown@hesbynett.no> writes: > On 11/09/2022 22:14, Ben Bacarisse wrote: >> David Brown <david.brown@hesbynett.no> writes: >> >>> On 11/09/2022 17:12, Ben Bacarisse wrote: >>>> David Brown <david.brown@hesbynett.no> writes: >>>> >>>>> The "fast" and "least" types /do/ have their uses. There are not many >>>>> targets that don't support 8-bit char and everything else as a power >>>>> of 8 bits, but there /are/ a few. The main class of exceptions are >>>>> DSP processors, and some highly specialised task-specific embedded >>>>> processors. With C23, I believe types (u)int8_t, (u)int16_t, >>>>> (u)int32_t and (u)int64_t have become mandatory - a target that does >>>>> not support these natively must simulate them. This renders the >>>>> "least" types obsolete. >>>> You are looking at them entirely from the point of view of portability, >>>> but their main use is in explaining what data the program needs. In the >>>> vast number of cases, writing int_least8_t (say) says exactly what's >>>> needed and using int8_t says something incorrect about the code. >>> >>> C does not have a way to express such requirements in detail. >>> Languages like Ada and Pascal let you have range types - you can say >>> you want an integer type that holds a number between 0 and 9 (as is >>> the case here). >> No, that was not the case. > > Sorry, it should be 0 to 15 the code also converts hex. But the same > principle remains. "For bases up to 256, these elements can be of type UC - an unsigned character can hold all values between 0 and 255" and there was a general base-b converter in both directions. <cut> >> The example is lost and was not, I think, limited in the way you've >> said. But I was explicitly not talking about one example. I said "In >> the vast number of cases...". By all means say I'm wrong, or say that >> you don't care to generalise, but countering a general remark with one >> specific case is not helpful. > > Sorry, I was still considering the particular example. If we are > generalising, we still need to have some restrictions to be able to > say something useful - perhaps "types to hold small non-negative > numbers" is appropriate. The generalisation was so wide that I did not think I needed to limit more than I had (to exclude matching external data layouts). > I agree with you that "uint_least8_t" and similar types could be > technically better choices in a number of cases, but not all decisions > are made for technical reasons. Indeed. Some are knee-jerk decisions, and some are, as you describe below based on a vague unease about what C really is. > I don't agree that it is better in a > "vast number" of cases - because it does /not/ say exactly what is > needed. In almost all cases of "small number", the upper bound is > /not/ 255. Thus "uint_least8_t" does not say exactly what you want - > it is wrong in a different way from "uint8_t", but still wrong. Eh? I can't see how you think I was saying what you are appear to be replying to. In the vast majority of cases, when I see intN_t I feel that int_leastN_t or int_fastN_t would be better, because none of extra guarantees of intN_t are needed. I usually just think "ah, this person does not know about the alternatives". > I think to some extent it comes down to a subjective opinion of what > we are willing to be wrong about and what we prefer to be correct > about in how we code. There are always compromises. What are some good examples if the use of the intN_t types? >>> So as I see it, in many cases (such as Tim's code), using >>> "uint_least8_t" would be just as much over-specifying the type >>> requirements as "uint8_t" does, while not giving the programmer or >>> reader any useful additional information. >> >> I disagree about this example, but I also don't want to get into "many". >> I miss-edited my test and ended up with "In the vast number of cases..." >> when I meant to say "In the vast majority of cases...". I hope the word >> vast made it clear that was not a quibble about numbers. I worry that, >> except when matching externally defined structures, the use of the >> intN_t (and maybe even the uintN_t) types is almost always knee-jerk >> programming. > > I don't see it as knee-jerk programming - though I can appreciate your > concern. (Maybe it /is/ knee-jerk programming for some people.) > > This is C programming. For a lot of C programming, people like to > know exactly what they get. (Many people believe C gives them > stronger guarantees that it really does in that respect, but that's > another matter.) The fixed size types gives them that assurance. I wrote knee-jerk to err on the kind side but I agree that it's often wanting reassurance, presumably because the programmer does not know what is assured by the alternatives. > Are > they tighter specified than usually required? Yes, typically - but > that is generally the case in /all/ programming in /all/ languages. That's not the point. The point is are these optional types more tightly specified than the guaranteed-to-exist alternatives? I say yes, but you think that's a matter of opinion. > We are forever using types or functions that are more general than > actually needed, ... Yes, of course. But that was not the point. >> My experience of C code post C99 is rather limited so I am open to >> persuasion here. > > I don't know how persuasive I am, I was hoping for persuasive examples where an intN_t type is better than an int_(least|fast)N_t type. <cut> -- Ben.
[toc] | [prev] | [next] | [standalone]
| From | Bart <bc@freeuk.com> |
|---|---|
| Date | 2022-09-12 17:46 +0100 |
| Message-ID | <tfnnob$1d8r$1@gioia.aioe.org> |
| In reply to | #167644 |
On 12/09/2022 16:48, Ben Bacarisse wrote: > David Brown <david.brown@hesbynett.no> writes: > In the vast majority of cases, when I see intN_t I feel that > int_leastN_t or int_fastN_t would be better, because none of extra > guarantees of intN_t are needed. I usually just think "ah, this person > does not know about the alternatives". > >> I think to some extent it comes down to a subjective opinion of what >> we are willing to be wrong about and what we prefer to be correct >> about in how we code. There are always compromises. > > What are some good examples if the use of the intN_t types? Of fixed width integer types in general? * Specifying narrow storage types for efficient memory use of arrays and structs * Optimising layouts of structs * Matching data types and layouts specified by an API, an external file format, etc * If writing C in particular, guaranteeing that an int-type for example is, say, 32 bits. Some might be shorter than needed, others could be longer than needed (say, 'long' when used on 64-bit Linux). * If generating C code, guaranteeing that the types match specific sizes in the source language But in the example at the top of this sub-thread, the 'UC' was used for an array of small values for which ordinary 'char' would suffice. For the unsigned int, given that every desktop machine now is 64-bit, and since one issue was the largest possible number, I'd use C's equivalent of u64. The disadvantage of fixed-width types as implemented in C is that printing no longer involved choosing from %u to %llu, it needs a funny macro. Which for uint_least32_t, I think is PRIuLEAST32. Lovely. And 'UI'? I think casting to unsigned long long then using %llu would be the best bet. I really can't say that C's 'least' and 'fast' types are a brilliant innovation that I can't wait to incorporate into my own stuff. If I was in charge of cutting out superfluous baggage, they'd be the first to go.
[toc] | [prev] | [next] | [standalone]
| From | Ben Bacarisse <ben.usenet@bsb.me.uk> |
|---|---|
| Date | 2022-09-12 20:37 +0100 |
| Message-ID | <875yhswfiv.fsf@bsb.me.uk> |
| In reply to | #167648 |
Bart <bc@freeuk.com> writes: > On 12/09/2022 16:48, Ben Bacarisse wrote: >> David Brown <david.brown@hesbynett.no> writes: > >> In the vast majority of cases, when I see intN_t I feel that >> int_leastN_t or int_fastN_t would be better, because none of extra >> guarantees of intN_t are needed. I usually just think "ah, this person >> does not know about the alternatives". >> >>> I think to some extent it comes down to a subjective opinion of what >>> we are willing to be wrong about and what we prefer to be correct >>> about in how we code. There are always compromises. >> What are some good examples if the use of the intN_t types? > > Of fixed width integer types in general? No. Cases where intN_t is better than ether int_lastN_t or int_fastN_t, except (and I've made this exception clear from the start) matching externally imposed size requirements. > * Specifying narrow storage types for efficient memory use of arrays > and structs So why not int_leastN_t? > * Optimising layouts of structs I'd prefer an actual example to know what you mean. It may count, but most optimising of structs is really down to the compiler. > * Matching data types and layouts specified by an API, an external > file format, etc Yes. This is the best use case, but it's not what's being discussed. > * If writing C in particular, guaranteeing that an int-type for > example is, say, 32 bits. Some might be shorter than needed, others > could be longer than needed (say, 'long' when used on 64-bit Linux). But why? That's the whole point. Outside of the obvious use case of matching an externally imposed size, programs usually only care about the minimum range of a data type, which is what int_(least|fast)N_t types give you. The intN_t types also give you a maximum range which most algorithms don't care about. The obvious exception (again already noted) is where the wrapping of unsigned arithmetic is exactly what happens to be wanted. > * If generating C code, guaranteeing that the types match specific > sizes in the source language Yup, but this is the same "match the API" use case. There's never been a question about this one. > But in the example at the top of this sub-thread, the 'UC' was used > for an array of small values for which ordinary 'char' would suffice. char might be signed. Did you see the comment about "bases up to 256"? > For the unsigned int, given that every desktop machine now is 64-bit, > and since one issue was the largest possible number, I'd use C's > equivalent of u64. Why would you not use int_max_t if the handling the largest possible number is the issue? > The disadvantage of fixed-width types as implemented in C is that > printing no longer involved choosing from %u to %llu, it needs a funny > macro. Which for uint_least32_t, I think is PRIuLEAST32. Lovely. And for uint32_t it's PRIu32. Both ugly. I hope not one is suggesting one chooses a type to avoid five letters. BTW, I can see why people would want to avoid /all/ of these types, especially (as in Tim's code) when writing for beginners. It's the apparent automatic reaching for intN_t that is the issue for me. -- Ben.
[toc] | [prev] | [next] | [standalone]
| From | Bart <bc@freeuk.com> |
|---|---|
| Date | 2022-09-13 00:03 +0100 |
| Message-ID | <tfodsg$16j7$1@gioia.aioe.org> |
| In reply to | #167655 |
On 12/09/2022 20:37, Ben Bacarisse wrote:
> Bart <bc@freeuk.com> writes:
>> * Specifying narrow storage types for efficient memory use of arrays
>> and structs
>
>
>> * Optimising layouts of structs
>
> I'd prefer an actual example to know what you mean. It may count, but
> most optimising of structs is really down to the compiler.
I like my structs to be powers of two in size, and as small as possible.
This can involve very careful crafting of the layout and arrangement,
using unions a lot, and making some fields do multiple jobs.
Key to that is being 100% confident of how big everything is. I normally
work also without auto-alignment and auto-padding.
When both 32 and 64 bits were a thing, then pointers were a different
size between the two systems, requiring separate structs for each. Now I
work on 64-bit machines only.
>> * If writing C in particular, guaranteeing that an int-type for
>> example is, say, 32 bits. Some might be shorter than needed, others
>> could be longer than needed (say, 'long' when used on 64-bit Linux).
>
> But why? That's the whole point. Outside of the obvious use case of
> matching an externally imposed size, programs usually only care about
> the minimum range of a data type, which is what int_(least|fast)N_t
> types give you. The intN_t types also give you a maximum range which
> most algorithms don't care about. The obvious exception (again already
> noted) is where the wrapping of unsigned arithmetic is exactly what
> happens to be wanted.
> So why not int_leastN_t?
Those Least types are just too weird:
* I have trouble getting my head around what they even mean, and
wouldn't be able to use them confidently
* I haven't seen them anywhere else outside of C
* I won't know how to port to/from C when Least types are involved
* And in C, they're just too cluttery!
Most languages now with control over type widths either have a set of
integer types with guaranteed widths (they are listed in the language
ref), or the widths are part of the type names anyway.
Only a few odd ones might be conditional (like usize (?) in Rust).
I like things short and sweet and with no fuss. Like 'byte' or 'u8'; not
the ones C offers (see below).
There just shouldn't be a discussion about it. Other language forums
don't spend their time debating which of half a dozen types should used
for a byte, or even how many bits it might have (I assume; I haven't
checked!).
>
>> For the unsigned int, given that every desktop machine now is 64-bit,
>> and since one issue was the largest possible number, I'd use C's
>> equivalent of u64.
>
> Why would you not use int_max_t if the handling the largest possible
> number is the issue?
I don't like types, and especially don't like proliferations of integer
types, like those offset_t and offset64_t and clock_t.
Most of them come down to one of i32 i64 u32 u64 anyway.
For a suitable type for as large an unsigned integer as the hardware can
deal with comfortably, why choose anything other than u64? This can also
easily be ported to another language.
(If you are creating bindings for other languages based on headers in C,
the last thing you want to see in an API is a non-specific type like
int_max_t or offset_t or even 'long'; you want a concrete type.)
> BTW, I can see why people would want to avoid /all/ of these types,
> especially (as in Tim's code) when writing for beginners. It's the
> apparent automatic reaching for intN_t that is the issue for me.
I think the problem is mainly with the 8-bit type. If you specifically
want it unsigned, the choices in C are:
unsigned char
uint8_t
uint_least8_t
uint_fast8_t
Not a great selection, is it? Forgetting the last two (which I hope
never to see in any actual code), neither of those first is really
appealing. I can see why Tim used 'UC'.
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2022-09-13 12:31 +0200 |
| Message-ID | <tfpm5j$2idls$1@dont-email.me> |
| In reply to | #167655 |
On 12/09/2022 21:37, Ben Bacarisse wrote:
> Bart <bc@freeuk.com> writes:
>
>> On 12/09/2022 16:48, Ben Bacarisse wrote:
>>> David Brown <david.brown@hesbynett.no> writes:
>>
>>> In the vast majority of cases, when I see intN_t I feel that
>>> int_leastN_t or int_fastN_t would be better, because none of extra
>>> guarantees of intN_t are needed. I usually just think "ah, this person
>>> does not know about the alternatives".
>>>
>>>> I think to some extent it comes down to a subjective opinion of what
>>>> we are willing to be wrong about and what we prefer to be correct
>>>> about in how we code. There are always compromises.
>>> What are some good examples if the use of the intN_t types?
>>
>> Of fixed width integer types in general?
>
> No. Cases where intN_t is better than ether int_lastN_t or int_fastN_t,
> except (and I've made this exception clear from the start) matching
> externally imposed size requirements.
My argument is not so much that "intN_t" types are really /better/ than
"int_leastN_t" or "int_fastN_t", but that the "least" and "fast"
versions are not noticeably better. In most situations, /none/ of these
types express /exactly/ what you want, with no more and no less.
Generally, they all specify features that don't matter at the time. So
the simpler and clearer choice then wins overall. (We may have
different ideas about what "better" means.)
Code does not exist in a vacuum (except for snippets posted in some kind
of discussion forum, which is why examples here would likely be
pointless). I have already made several comments about why I think a
smaller number of types is better than a proliferation of types that
appear similar. Another that has been mentioned is interfacing to
existing code and functions - if your code is using library calls that
expect a "u32" parameter, then perhaps "u32" is the best type for your
local variable. If you are maintaining code that uses "byte" and
"dword" and adding a new function, then "dword" could well be the best
type. If you are writing code that will be seen by others, and you
don't have the time to explain the difference between unfamiliar
"int_fast32_t" and familiar "int32_t", then "int32_t" is a better choice.
C has quite a number of integer types (including those from the standard
library), with lots of different combinations of features. It does not
have all the combinations that I want - and I am surely not alone in
that (though exactly what people want will differ). Equally, most code
only requires a few integer types at a time. For a lot of it, "int" is
sufficient.
Perhaps if a large proportion of C programmers had been re-educated when
C99 came out, and encouraged in new directions, then the "fast" and
"least" types could have become popular - and then they would have been
the "better" choice much more often. But that didn't happen. Remember,
only a tiny proportion of C programmers have read the standards -
features that are rarely used in real code, will be unfamiliar to most C
programmers.
I am not in any way suggesting that it is a good idea to follow bad
habits of other programmers, nor even saying that we should limit the
language features we use in order to suit the lowest common denominator.
But I also don't think it is helpful to use a relatively unknown
feature that has, at most, subtle and symbolic benefits.
While thinking about newer C features, I have found another challenge
with using multiple different sometimes incompatible aliases for integer
types - _Generic. I don't know if you've used _Generic much, but it can
sometimes be useful. Here is a small example :
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
#define print_var(_x) \
do { \
printf("Var " # _x " = "); \
printf( \
_Generic(_x, \
uint8_t : "0x%02" PRIx8, \
uint16_t : "0x%04" PRIx16, \
uint32_t : "0x%08" PRIx32, \
uint64_t : "0x%016" PRIx64), \
_x); \
printf("\n"); \
} while (0);
(Unfortunately the "result" of a _Generic doesn't count as a string
literal, and can't be directly concatenated with the other bits of the
string.)
You can use it like this :
void printtest(uint8_t x, uint16_t y, uint32_t z, uint64_t w) {
print_var(x);
print_var(y);
print_var(z);
print_var(w);
}
So far, so good. What about the "least" types? We can't add them to
the list as the _Generic would be ambiguous. Fortunately, in the real
world (excluding DSP's) the "least" types are always identical to the
fixed-size types, so the _Generic works unchanged.
void printtest2(uint_least8_t x, uint_least16_t y,
uint_least32_t z, uint_least64_t w) {
print_var(x);
print_var(y);
print_var(z);
print_var(w);
}
But when we get to the "fast" types, the fun starts :
void printtest3(uint_fast8_t x, uint_fast16_t y,
uint_fast32_t z, uint_fast64_t w) {
print_var(x);
print_var(y);
print_var(z);
print_var(w);
}
For one thing, the person expecting to see a 16-bit "y" sees 16 hex
digits, not 4. But try compiling this for 32-bit ARM (here's a godbolt
link: <https://godbolt.org/z/TP3sMnTsG> ). It turns out that on that
platform, "uint32_t" is "unsigned long int", while "uint_fast32_t" is
"unsigned int". So now our nice neat _Generic needs to have an extra line :
uint_fast32_t : "0x%08" PRIxFAST32, \
and that also matches "uint_fast16_t".
But now try compiling this for 64-bit x86. Here, "uint_fast16_t",
"uint_fast32_t" and "uint_fast64_t" are all "unsigned long long int" -
colliding with "uint64_t".
If you stick to a simple set of fixed-size types, the _Generic
statements are clear, follow a nice pattern, and give the user the
expected results. Mixing the "fast" types spoils all that.
Certainly _Generic is a bit niche, and not commonly used - I'd believe
you if you said that many more people know about "uint_fast16_t" than
_Generic. But it is an indication of how a simpler and more limited
selection of types can lead to simpler and neater code, and fewer
unexpected effects.
(I think it is also fair to argue that this example shows a flaw in
_Generic - it would arguably have been better to allow compatible types
to appear multiple times as long as the resulting expression is the same
in each case.)
>
>> * Specifying narrow storage types for efficient memory use of arrays
>> and structs
>
> So why not int_leastN_t?
The question is rather, /why/ int_leastN_t ? It doesn't give you a
smaller type, it just gives the reader bigger uncertainty. Some people
will be quite happy not knowing the underlying details, others prefer to
know them.
>
>> * Optimising layouts of structs
>
> I'd prefer an actual example to know what you mean. It may count, but
> most optimising of structs is really down to the compiler.
Compilers do not do much optimising of structs. It's been tried (by gcc
at least), but was then removed because the complications of dealing
with re-arranged structures across different translation units meant it
could rarely be used safely. I believe there are new developments
underway to do it in connection with link-time optimisation.
The ABI for any given implementation will determine structure padding
and alignment rules, and the compiler will handle these automatically.
However, sometimes you want tighter control - even when you are not
talking about matching external structures (file structures, network
structures, hardware peripherals, etc.). A common example is to match
cache line sizes for arrays, which can make a significant difference to
the efficiency of some kinds of code. For smaller processors, it may
even be useful to match powers of two for more efficient indexing of
arrays, though such devices without multiplication instructions are a
dying breed.
[toc] | [prev] | [next] | [standalone]
| From | Ben Bacarisse <ben.usenet@bsb.me.uk> |
|---|---|
| Date | 2022-09-13 12:48 +0100 |
| Message-ID | <8735cvtryu.fsf@bsb.me.uk> |
| In reply to | #167662 |
David Brown <david.brown@hesbynett.no> writes:
> On 12/09/2022 21:37, Ben Bacarisse wrote:
>> Bart <bc@freeuk.com> writes:
>>
>>> On 12/09/2022 16:48, Ben Bacarisse wrote:
>>>> David Brown <david.brown@hesbynett.no> writes:
>>>
>>>> In the vast majority of cases, when I see intN_t I feel that
>>>> int_leastN_t or int_fastN_t would be better, because none of extra
>>>> guarantees of intN_t are needed. I usually just think "ah, this person
>>>> does not know about the alternatives".
>>>>
>>>>> I think to some extent it comes down to a subjective opinion of what
>>>>> we are willing to be wrong about and what we prefer to be correct
>>>>> about in how we code. There are always compromises.
>>>> What are some good examples if the use of the intN_t types?
>>>
>>> Of fixed width integer types in general?
>> No. Cases where intN_t is better than ether int_lastN_t or int_fastN_t,
>> except (and I've made this exception clear from the start) matching
>> externally imposed size requirements.
>
> My argument is not so much that "intN_t" types are really /better/
> than "int_leastN_t" or "int_fastN_t", but that the "least" and "fast"
> versions are not noticeably better. In most situations, /none/ of
> these types express /exactly/ what you want, with no more and no less.
> Generally, they all specify features that don't matter at the time.
> So the simpler and clearer choice then wins overall. (We may have
> different ideas about what "better" means.)
It's a relative term. When I see "int32_t" I imagine the programmer
considers that type to be better than the alternatives. So I wonder why
the programmer thinks that limiting range is important. Must this
object map to some hardware feature? If not, I am curious about how the
code would fail if the fastest type that can handle 32 bits were used
instead.
When I see int_fast32_t I have fewer questions. Even fewer if I see
long int (guaranteed to be at least 32 bits) and likely to be the
fastest such type.
> Code does not exist in a vacuum (except for snippets posted in some
> kind of discussion forum, which is why examples here would likely be
> pointless).
Why does a real world example become pointless when posted here?
> Perhaps if a large proportion of C programmers had been re-educated
> when C99 came out, and encouraged in new directions, then the "fast"
> and "least" types could have become popular - and then they would have
> been the "better" choice much more often. But that didn't happen.
But I think people /do/ use the intN_t types when matching external data
is not involved. (If they don't, I've misunderstood, and this whole
thread is pointless!) But if they do use them, how did they learn about
them without learning about the alternatives? I suspect they saw them
used for one thing (maybe in an API struct) and then used them for other
tasks without much thought.
> While thinking about newer C features, I have found another challenge
> with using multiple different sometimes incompatible aliases for
> integer types - _Generic. I don't know if you've used _Generic much,
> but it can sometimes be useful. Here is a small example :
>
>
> #include <stdint.h>
> #include <inttypes.h>
> #include <stdio.h>
>
> #define print_var(_x) \
> do { \
> printf("Var " # _x " = "); \
> printf( \
> _Generic(_x, \
> uint8_t : "0x%02" PRIx8, \
> uint16_t : "0x%04" PRIx16, \
> uint32_t : "0x%08" PRIx32, \
> uint64_t : "0x%016" PRIx64), \
> _x); \
> printf("\n"); \
> } while (0);
<cut>
> So far, so good. What about the "least" types? We can't add them to
> the list as the _Generic would be ambiguous. Fortunately, in the real
> world (excluding DSP's) the "least" types are always identical to the
> fixed-size types, so the _Generic works unchanged.
>
> void printtest2(uint_least8_t x, uint_least16_t y,
> uint_least32_t z, uint_least64_t w) {
> print_var(x);
> print_var(y);
> print_var(z);
> print_var(w);
> }
>
> But when we get to the "fast" types, the fun starts :
>
> void printtest3(uint_fast8_t x, uint_fast16_t y,
> uint_fast32_t z, uint_fast64_t w) {
> print_var(x);
> print_var(y);
> print_var(z);
> print_var(w);
> }
>
> For one thing, the person expecting to see a 16-bit "y" sees 16 hex
> digits, not 4.
Which is presumably what the author intended, no? I can't see what
point the code has other than to reveal the underlying width.
> But try compiling this for 32-bit ARM (here's a godbolt link:
> <https://godbolt.org/z/TP3sMnTsG> ). It turns out that on that
> platform, "uint32_t" is "unsigned long int", while "uint_fast32_t" is
> "unsigned int". So now our nice neat _Generic needs to have an extra
> line :
>
> uint_fast32_t : "0x%08" PRIxFAST32, \
>
> and that also matches "uint_fast16_t".
>
> But now try compiling this for 64-bit x86. Here, "uint_fast16_t",
> "uint_fast32_t" and "uint_fast64_t" are all "unsigned long long int" -
> colliding with "uint64_t".
>
> If you stick to a simple set of fixed-size types, the _Generic
> statements are clear, follow a nice pattern, and give the user the
> expected results. Mixing the "fast" types spoils all that.
It looks like you wrote a rather limited macro in order to make the case
that a limited number of types should be used? I must be missing the
point you are making. To help move things along, here's what I'd write:
#define print_var(x) \
printf("Var "#x" = 0x%0*"PRIxMAX"\n", (int)(sizeof x * 2), (intmax_t)x);
What am I missing about the intent of your macro?
> Certainly _Generic is a bit niche, and not commonly used - I'd believe
> you if you said that many more people know about "uint_fast16_t" than
> _Generic. But it is an indication of how a simpler and more limited
> selection of types can lead to simpler and neater code, and fewer
> unexpected effects.
I not getting the point. Programmers should avoid using all the type
available so that _Generic is more usable? Surely not. I am missing
something.
>>> * Specifying narrow storage types for efficient memory use of arrays
>>> and structs
>> So why not int_leastN_t?
>
> The question is rather, /why/ int_leastN_t?
No! The question is why int_leastN_t rather the X, and I am currently
focusing on the case where X is intN_t. There will be different answers
for other Xs.
> It doesn't give you a
> smaller type, it just gives the reader bigger uncertainty.
When compared to intN_t it tells the reader that this is one of the
cases Bart was talking about -- it's the space that's important. intN_t
does not say that. intN_t says we need N and no more than N bits for
some reason the reader will be left to speculate about. I would assume
that this struct or array was to be matched against some external data
format.
Seeing int_least32_t I would assume the array will be large and space
matters. Why would you /not/ want to tell the reader this?
--
Ben.
[toc] | [prev] | [next] | [standalone]
| From | Bart <bc@freeuk.com> |
|---|---|
| Date | 2022-09-13 14:18 +0100 |
| Message-ID | <tfpvun$tdk$1@gioia.aioe.org> |
| In reply to | #167663 |
On 13/09/2022 12:48, Ben Bacarisse wrote:
> David Brown <david.brown@hesbynett.no> writes:
>
>> On 12/09/2022 21:37, Ben Bacarisse wrote:
>>> Bart <bc@freeuk.com> writes:
>>>
>>>> On 12/09/2022 16:48, Ben Bacarisse wrote:
>>>>> David Brown <david.brown@hesbynett.no> writes:
>>>>
>>>>> In the vast majority of cases, when I see intN_t I feel that
>>>>> int_leastN_t or int_fastN_t would be better, because none of extra
>>>>> guarantees of intN_t are needed. I usually just think "ah, this person
>>>>> does not know about the alternatives".
>>>>>
>>>>>> I think to some extent it comes down to a subjective opinion of what
>>>>>> we are willing to be wrong about and what we prefer to be correct
>>>>>> about in how we code. There are always compromises.
>>>>> What are some good examples if the use of the intN_t types?
>>>>
>>>> Of fixed width integer types in general?
>>> No. Cases where intN_t is better than ether int_lastN_t or int_fastN_t,
>>> except (and I've made this exception clear from the start) matching
>>> externally imposed size requirements.
>>
>> My argument is not so much that "intN_t" types are really /better/
>> than "int_leastN_t" or "int_fastN_t", but that the "least" and "fast"
>> versions are not noticeably better. In most situations, /none/ of
>> these types express /exactly/ what you want, with no more and no less.
>> Generally, they all specify features that don't matter at the time.
>> So the simpler and clearer choice then wins overall. (We may have
>> different ideas about what "better" means.)
>
> It's a relative term. When I see "int32_t" I imagine the programmer
> considers that type to be better than the alternatives.
The alternatives being...? I assume the ones you have in mind are int,
int_least32_t and int_fast32_t.
With `int`, this is invariably 32 bits on most machines I'm likely to
use, but C says it could be 16 bits. Those are both limiting to me, as
I'm used to 'int' elsewhere being 64 bits. (When I encounter C's `int`
type in APIs, to me that is the narrow storage type 'int32' that needs
to be sign-extended.)
Use of int32_t at least guarantees you won't get 16 bits. But if your
requirements are known to fit into 32 bits, you also know that you won't
be wastefully given 64 bits as might conceivably be the case with
int_least32_t.
If 32 bits might be too small, just use int64 and be done with it rather
than faffing about with int_least32_t.
When your requirements are such that you want int32 on 32-bit targets
and int64 on 64-bit targets (which means the app's range and behaviour
is more limiting on the former), then I really wouldn't trust any of C's
types to give me that, least of all `int_least32_t`, and certainly not
'long'.
(In my own stuff I used to have a type 'intm' that did that, now
abandoned as I don't really bother with 32 bits now; when I do, I still
use 64 bits anyway.)
> So I wonder why
> the programmer thinks that limiting range is important.
> Must this
> object map to some hardware feature?
We're talking about a lower level language, so yes it must.
If you are writing an application and you need a 'don't care' integer
type, then that used to be 'int', but while I don't want to specify an
exact type, I really want that to default to 64 bits not 32. With 64
bits, values need to be 4 billion times bigger before they overflow.
So, while C could conceivably have a 64-bit int, usually it's only 32
bits, which I think is a problem. Yes, you can define a wider one but
it means using 'long long' (which might be 128 bits?), or employing
width-specific types. But that is not not caring any more; it's caring
quite bit!
>> But when we get to the "fast" types, the fun starts :
>>
>> void printtest3(uint_fast8_t x, uint_fast16_t y,
>> uint_fast32_t z, uint_fast64_t w) {
>> print_var(x);
>> print_var(y);
>> print_var(z);
>> print_var(w);
>> }
>>
>> For one thing, the person expecting to see a 16-bit "y" sees 16 hex
>> digits, not 4.
>
> Which is presumably what the author intended, no? I can't see what
> point the code has other than to reveal the underlying width.
The _Generic example was a very good one in showing the problems with
C's zoo of integer types. Because some types may be aliases of each
other, in an implementation defined manner, which can result in
different behaviour or compile-time errors with _Generic.
For example, int64_t may be defined on top of either 'long' or 'long
long'. This also comes up outside _Generic, where pointers P and Q to
those nominallly different types may or may not be compatible.
Most languages now give you just these 8 types: i8-i64 u8-u64.
C gives you 11 (signed/unsigned char short int long long-long, plus
plain char), plus 8 more from stdint.h, plus 16 least/fast variations,
which makes what, 35 types? To represent those same 8 underlying types.
And that's before taking account of size_t, int_max_t, int_ptr_t, ...
(Or considering different syntax for the same type: 'long int long' and
'int long long...')
Wouldn't it better to try minimising the range of types than advocating
using even more?
[toc] | [prev] | [next] | [standalone]
| From | Anton Shepelev <anton.txt@g{oogle}mail.com> |
|---|---|
| Date | 2022-09-13 17:13 +0300 |
| Message-ID | <20220913171303.7c8bdf65a9a00cdbaf318cd5@g{oogle}mail.com> |
| In reply to | #167667 |
Bart: > Wouldn't it better to try minimising the range of types > than advocating using even more? Yes, but you'd still have to support two sets of types: fixed-width ones, and ones with a width dependent on hardware/implementation. You seem to ignore the latter variety. -- () ascii ribbon campaign - against html e-mail /\ http://preview.tinyurl.com/qcy6mjc [archived]
[toc] | [prev] | [next] | [standalone]
| From | Ben Bacarisse <ben.usenet@bsb.me.uk> |
|---|---|
| Date | 2022-09-13 16:50 +0100 |
| Message-ID | <87tu5bs282.fsf@bsb.me.uk> |
| In reply to | #167667 |
Bart <bc@freeuk.com> writes:
> On 13/09/2022 12:48, Ben Bacarisse wrote:
>> David Brown <david.brown@hesbynett.no> writes:
>>
>>> On 12/09/2022 21:37, Ben Bacarisse wrote:
>>>> Bart <bc@freeuk.com> writes:
>>>>
>>>>> On 12/09/2022 16:48, Ben Bacarisse wrote:
>>>>>> David Brown <david.brown@hesbynett.no> writes:
>>>>>
>>>>>> In the vast majority of cases, when I see intN_t I feel that
>>>>>> int_leastN_t or int_fastN_t would be better, because none of extra
>>>>>> guarantees of intN_t are needed. I usually just think "ah, this person
>>>>>> does not know about the alternatives".
>>>>>>
>>>>>>> I think to some extent it comes down to a subjective opinion of what
>>>>>>> we are willing to be wrong about and what we prefer to be correct
>>>>>>> about in how we code. There are always compromises.
>>>>>> What are some good examples if the use of the intN_t types?
>>>>>
>>>>> Of fixed width integer types in general?
>>>> No. Cases where intN_t is better than ether int_lastN_t or int_fastN_t,
>>>> except (and I've made this exception clear from the start) matching
>>>> externally imposed size requirements.
>>>
>>> My argument is not so much that "intN_t" types are really /better/
>>> than "int_leastN_t" or "int_fastN_t", but that the "least" and "fast"
>>> versions are not noticeably better. In most situations, /none/ of
>>> these types express /exactly/ what you want, with no more and no less.
>>> Generally, they all specify features that don't matter at the time.
>>> So the simpler and clearer choice then wins overall. (We may have
>>> different ideas about what "better" means.)
>> It's a relative term. When I see "int32_t" I imagine the programmer
>> considers that type to be better than the alternatives.
>
> The alternatives being...? I assume the ones you have in mind are int,
> int_least32_t and int_fast32_t.
Not int. int is not an alternative since it could have fewer than 32
bits.
> Use of int32_t at least guarantees you won't get 16 bits.
As do the alternatives we've been discussing here -- the types no one
seems to use.
> But if your requirements are known to fit into 32 bits, you also know
> that you won't be wastefully given 64 bits as might conceivably be the
> case with int_least32_t.
No. If int32_t exists, int_least32_t can't be 64 bits. These types are
for when you want to tell the reader "the smallest type >= N".
> If 32 bits might be too small, just use int64 and be done with it
> rather than faffing about with int_least32_t.
Faffing about is just spin. And if you need 64 bits, using
int_least32_t is not faffing about, it just wrong.
>> So I wonder why
>> the programmer thinks that limiting range is important.
>
>> Must this
>> object map to some hardware feature?
>
> We're talking about a lower level language, so yes it must.
I think you are misunderstanding. We should have a term for the use of
a fixed-width type intended to match something externally defined -- a
network header field, as OS structure, data in some fixed file format.
This use is 100% justified. It is, in my view, the main reason these
types exist. Let's call this is a "matching use".
That's what I meant here. When I see int32_t I ask myself what is this
matching use?
So we are talking /only/ about uses of the fixed-width types that are
not matching uses.
> If you are writing an application and you need a 'don't care' integer
> type, then that used to be 'int', but while I don't want to specify an
> exact type, I really want that to default to 64 bits not 32. With 64
> bits, values need to be 4 billion times bigger before they overflow.
>
> So, while C could conceivably have a 64-bit int, usually it's only 32
> bits, which I think is a problem.
Use int_fast64_t.
>>> But when we get to the "fast" types, the fun starts :
>>>
>>> void printtest3(uint_fast8_t x, uint_fast16_t y,
>>> uint_fast32_t z, uint_fast64_t w) {
>>> print_var(x);
>>> print_var(y);
>>> print_var(z);
>>> print_var(w);
>>> }
>>>
>>> For one thing, the person expecting to see a 16-bit "y" sees 16 hex
>>> digits, not 4.
>> Which is presumably what the author intended, no? I can't see what
>> point the code has other than to reveal the underlying width.
>
> The _Generic example was a very good one in showing the problems with
> C's zoo of integer types.
Except there seems to a very simple way to do what was wanted. It
looked a bit contrived to me.
> Wouldn't it better to try minimising the range of types than
> advocating using even more?
I am not advocating for more. Again that's just the spin you want to
put in my post.
--
Ben.
[toc] | [prev] | [next] | [standalone]
Page 2 of 5 — ← Prev page 1 [2] 3 4 5 Next page →
Back to top | Article view | comp.lang.c
csiph-web