Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.c > #394592 > unrolled thread
| Started by | pozz <pozzugno@gmail.com> |
|---|---|
| First post | 2025-10-20 17:03 +0200 |
| Last post | 2025-12-15 00:25 -0800 |
| Articles | 20 on this page of 27 — 14 participants |
Back to article view | Back to comp.lang.c
signed vs unsigned and gcc -Wsign-conversion pozz <pozzugno@gmail.com> - 2025-10-20 17:03 +0200
Re: signed vs unsigned and gcc -Wsign-conversion Bonita Montero <Bonita.Montero@gmail.com> - 2025-10-20 17:38 +0200
Re: signed vs unsigned and gcc -Wsign-conversion Michael S <already5chosen@yahoo.com> - 2025-10-20 19:43 +0300
Re: signed vs unsigned and gcc -Wsign-conversion Bonita Montero <Bonita.Montero@gmail.com> - 2025-10-20 19:07 +0200
Re: signed vs unsigned and gcc -Wsign-conversion scott@slp53.sl.home (Scott Lurndal) - 2025-10-20 18:01 +0000
Re: signed vs unsigned and gcc -Wsign-conversion Janis Papanagnou <janis_papanagnou+ng@hotmail.com> - 2025-10-21 04:27 +0200
Re: signed vs unsigned and gcc -Wsign-conversion David Brown <david.brown@hesbynett.no> - 2025-10-21 09:13 +0200
Re: signed vs unsigned and gcc -Wsign-conversion BGB <cr88192@gmail.com> - 2025-10-20 17:44 -0500
Re: signed vs unsigned and gcc -Wsign-conversion Lawrence D’Oliveiro <ldo@nz.invalid> - 2025-10-20 23:36 +0000
Re: signed vs unsigned and gcc -Wsign-conversion Kaz Kylheku <643-408-1753@kylheku.com> - 2025-10-20 23:52 +0000
Re: signed vs unsigned and gcc -Wsign-conversion Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2025-10-20 16:58 -0700
Re: signed vs unsigned and gcc -Wsign-conversion David Brown <david.brown@hesbynett.no> - 2025-10-20 20:03 +0200
Re: signed vs unsigned and gcc -Wsign-conversion Kaz Kylheku <643-408-1753@kylheku.com> - 2025-10-20 20:09 +0000
Re: signed vs unsigned and gcc -Wsign-conversion rbowman <bowman@montana.com> - 2025-10-21 01:43 +0000
Re: signed vs unsigned and gcc -Wsign-conversion David Brown <david.brown@hesbynett.no> - 2025-10-21 12:42 +0200
Re: signed vs unsigned and gcc -Wsign-conversion James Kuyper <jameskuyper@alumni.caltech.edu> - 2025-10-21 14:44 -0400
Re: signed vs unsigned and gcc -Wsign-conversion David Brown <david.brown@hesbynett.no> - 2025-10-21 22:56 +0200
Re: signed vs unsigned and gcc -Wsign-conversion Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2025-10-20 14:48 -0700
Re: signed vs unsigned and gcc -Wsign-conversion Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2025-10-20 17:13 -0700
Re: signed vs unsigned and gcc -Wsign-conversion Kaz Kylheku <643-408-1753@kylheku.com> - 2025-10-21 01:45 +0000
Re: signed vs unsigned and gcc -Wsign-conversion antispam@fricas.org (Waldek Hebisch) - 2025-10-21 03:52 +0000
Re: signed vs unsigned and gcc -Wsign-conversion Lawrence D’Oliveiro <ldo@nz.invalid> - 2025-10-20 23:35 +0000
Re: signed vs unsigned and gcc -Wsign-conversion Lawrence D’Oliveiro <ldo@nz.invalid> - 2025-10-20 23:38 +0000
Re: signed vs unsigned and gcc -Wsign-conversion Bonita Montero <Bonita.Montero@gmail.com> - 2025-10-21 09:57 +0200
Re: signed vs unsigned and gcc -Wsign-conversion Lawrence D’Oliveiro <ldo@nz.invalid> - 2025-10-21 19:45 +0000
Re: signed vs unsigned and gcc -Wsign-conversion antispam@fricas.org (Waldek Hebisch) - 2025-10-21 04:42 +0000
Re: signed vs unsigned and gcc -Wsign-conversion Tim Rentsch <tr.17687@z991.linuxsc.com> - 2025-12-15 00:25 -0800
Page 1 of 2 [1] 2 Next page →
| From | pozz <pozzugno@gmail.com> |
|---|---|
| Date | 2025-10-20 17:03 +0200 |
| Subject | signed vs unsigned and gcc -Wsign-conversion |
| Message-ID | <10d5j0v$3kdmk$1@dont-email.me> |
After many years programming in C language, I'm always unsure if it is safer to use signed int or unsigned int. Of course there are situations where signed or unsigned is clearly better. For example, if the values could assume negative values, signed int is the only solution. If you are manipulating single bits (&, |, ^, <<, >>), unsigned ints are your friends. What about other situations? For example, what do you use for the "i" loop variable? I recently activated gcc -Wsign-conversion option on a codebase and received a lot of warnings. I started to fix them, usually expliciting casting. Is it the way or is it better to avoid the warning from the beginning, choosing the right signed or unsigned type?
[toc] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2025-10-20 17:38 +0200 |
| Message-ID | <10d5l22$3a8tm$2@raubtier-asyl.eternal-september.org> |
| In reply to | #394592 |
Am 20.10.2025 um 17:03 schrieb pozz: > I recently activated gcc -Wsign-conversion option on a codebase and > received a lot of warnings. I started to fix them, usually expliciting > casting. ... As long as this doesn't fix malfunctions it's purely aesthetical.
[toc] | [prev] | [next] | [standalone]
| From | Michael S <already5chosen@yahoo.com> |
|---|---|
| Date | 2025-10-20 19:43 +0300 |
| Message-ID | <20251020194337.000029ab@yahoo.com> |
| In reply to | #394592 |
On Mon, 20 Oct 2025 17:03:58 +0200 pozz <pozzugno@gmail.com> wrote: > After many years programming in C language, I'm always unsure if it > is safer to use signed int or unsigned int. > > Of course there are situations where signed or unsigned is clearly > better. For example, if the values could assume negative values, > signed int is the only solution. If you are manipulating single bits > (&, |, ^, <<, >>), unsigned ints are your friends. > > What about other situations? For example, what do you use for the "i" > loop variable? > I'd just point out that small negative numbers are FAR more common than numbers in range [2**31..2**32-1]. Now, make your own conclusion. > I recently activated gcc -Wsign-conversion option on a codebase and > received a lot of warnings. I started to fix them, usually > expliciting casting. Is it the way or is it better to avoid the > warning from the beginning, choosing the right signed or unsigned > type? > >
[toc] | [prev] | [next] | [standalone]
| From | Bonita Montero <Bonita.Montero@gmail.com> |
|---|---|
| Date | 2025-10-20 19:07 +0200 |
| Message-ID | <10d5q7o$3brb4$1@raubtier-asyl.eternal-september.org> |
| In reply to | #394594 |
Am 20.10.2025 um 18:43 schrieb Michael S: > On Mon, 20 Oct 2025 17:03:58 +0200 > pozz <pozzugno@gmail.com> wrote: > >> After many years programming in C language, I'm always unsure if it >> is safer to use signed int or unsigned int. >> >> Of course there are situations where signed or unsigned is clearly >> better. For example, if the values could assume negative values, >> signed int is the only solution. If you are manipulating single bits >> (&, |, ^, <<, >>), unsigned ints are your friends. >> >> What about other situations? For example, what do you use for the "i" >> loop variable? >> > > I'd just point out that small negative numbers are FAR more common than > numbers in range [2**31..2**32-1]. So use a short instead of an int for a loop counter to make the code run faster on a 68000-CPU ? ;-) > Now, make your own conclusion. > > >> I recently activated gcc -Wsign-conversion option on a codebase and >> received a lot of warnings. I started to fix them, usually >> expliciting casting. Is it the way or is it better to avoid the >> warning from the beginning, choosing the right signed or unsigned >> type? >> >> >
[toc] | [prev] | [next] | [standalone]
| From | scott@slp53.sl.home (Scott Lurndal) |
|---|---|
| Date | 2025-10-20 18:01 +0000 |
| Message-ID | <2OuJQ.346589$Tux4.230279@fx11.iad> |
| In reply to | #394594 |
Michael S <already5chosen@yahoo.com> writes: >On Mon, 20 Oct 2025 17:03:58 +0200 >pozz <pozzugno@gmail.com> wrote: > >> After many years programming in C language, I'm always unsure if it >> is safer to use signed int or unsigned int. >> >> Of course there are situations where signed or unsigned is clearly >> better. For example, if the values could assume negative values, >> signed int is the only solution. If you are manipulating single bits >> (&, |, ^, <<, >>), unsigned ints are your friends. >> >> What about other situations? For example, what do you use for the "i" >> loop variable? >> > >I'd just point out that small negative numbers are FAR more common than >numbers in range [2**31..2**32-1]. >Now, make your own conclusion. One might also point out that negative loop indicies are rare, and thus ones conclusion may be that generally speaking loop indexes should be unsigned.
[toc] | [prev] | [next] | [standalone]
| From | Janis Papanagnou <janis_papanagnou+ng@hotmail.com> |
|---|---|
| Date | 2025-10-21 04:27 +0200 |
| Message-ID | <10d6r20$3km8p$1@dont-email.me> |
| In reply to | #394596 |
On 20.10.2025 20:01, Scott Lurndal wrote: > Michael S <already5chosen@yahoo.com> writes: >> [...] > > One might also point out that negative loop indicies are rare, and > thus ones conclusion may be that generally speaking loop indexes should > be unsigned. Just note that loop-indices typically run over arrays. And while okay in an (more common) ascending traversal it may become error-prone in a descending array-traversal loop. uint i; for (i=N-1; i>=0; i--) a[i]; Janis
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2025-10-21 09:13 +0200 |
| Message-ID | <10d7br2$3o6i0$1@dont-email.me> |
| In reply to | #394596 |
On 20/10/2025 20:01, Scott Lurndal wrote: > Michael S <already5chosen@yahoo.com> writes: >> On Mon, 20 Oct 2025 17:03:58 +0200 >> pozz <pozzugno@gmail.com> wrote: >> >>> After many years programming in C language, I'm always unsure if it >>> is safer to use signed int or unsigned int. >>> >>> Of course there are situations where signed or unsigned is clearly >>> better. For example, if the values could assume negative values, >>> signed int is the only solution. If you are manipulating single bits >>> (&, |, ^, <<, >>), unsigned ints are your friends. >>> >>> What about other situations? For example, what do you use for the "i" >>> loop variable? >>> >> >> I'd just point out that small negative numbers are FAR more common than >> numbers in range [2**31..2**32-1]. >> Now, make your own conclusion. > > One might also point out that negative loop indicies are rare, and > thus ones conclusion may be that generally speaking loop indexes should > be unsigned. > Loop indicies greater than 2 ^ 31 are equally rare. (Loops of between 2 ^ 15 and 2 ^ 16 - 1 on 8-bit and 16-bit targets are less unrealistic.) Loops where you actually want the index counter to wrap are very rare, except perhaps when your loop is shifting the index each count (and then you are firmly in unsigned type territory). So in general, you are dealing with numbers that will fit comfortably within the ranges of both "int" and "unsigned int". If there is no other deciding factor, pick the one with the fewest unnecessary additional specifications, since that gives the compiler the most flexibility - "int".
[toc] | [prev] | [next] | [standalone]
| From | BGB <cr88192@gmail.com> |
|---|---|
| Date | 2025-10-20 17:44 -0500 |
| Message-ID | <10d6e47$3hpod$1@dont-email.me> |
| In reply to | #394594 |
On 10/20/2025 11:43 AM, Michael S wrote:
> On Mon, 20 Oct 2025 17:03:58 +0200
> pozz <pozzugno@gmail.com> wrote:
>
>> After many years programming in C language, I'm always unsure if it
>> is safer to use signed int or unsigned int.
>>
>> Of course there are situations where signed or unsigned is clearly
>> better. For example, if the values could assume negative values,
>> signed int is the only solution. If you are manipulating single bits
>> (&, |, ^, <<, >>), unsigned ints are your friends.
>>
>> What about other situations? For example, what do you use for the "i"
>> loop variable?
>>
>
> I'd just point out that small negative numbers are FAR more common than
> numbers in range [2**31..2**32-1].
> Now, make your own conclusion.
>
Yeah, the distribution is lopsided, but I had usually noted that for
numeric values of n bits, by the time n reaches 9 or 10, one becomes
more likely to encounter a negative value than one in the range of n+1.
Whereas, below this point, one is more likely to encounter a positive
value larger than n, than to encounter a negative value.
So:
Positive values between 0 and 511: Very common;
Negative values:
Less common than values under +512
More common than values over 1024.
There is typically a large cluster of small positive numbers near 0,
with a very steep falloff as numbers get larger.
So, for example:
1 is most common;
2 is less common than 1;
3 is less common than 2;
...
Like, where the probability of seeing N is seemingly 1/(N+1).
Outside of this main cluster, which largely falls to "very little" by
512, there are a few big spikes up near a few locations:
n = 2^15 and 2^16 (Best covered by a 17-bit sign-extended value)
n = 2^31 and 2^32 (Best covered by a 33-bit sign-extended value)
n = 2^63
If expressing values as fixed-width binary fields, there is often sort
of a "no man's land" for values between 34 and 61 bits where one is
unlikely to find a whole lot of anything.
Contrast, between 18 and 30 bits, there are still a handful of values
spread across this range, usually in small counts (so, this space isn't
really as empty as the gap starting at 34 bits).
So, say, it is not all that useful to be able to represent a value
larger than 33 bits without going all the way to 64.
And, at this upper end, most of what one encounters tends to be things
like double-precision values and EIGHTCC style values.
And, statistically speaking, int32 is likely to hold the vast majority
of integer values one is likely to encounter.
A lot is likely to depend on what one is looking at (this is mostly for
a distribution of literal values in my compiler stats).
Ironically, because of the distribution, having things like some CPU
instructions with only 5 or 6 bit fields for integer immediate values
isn't totally useless.
>
>> I recently activated gcc -Wsign-conversion option on a codebase and
>> received a lot of warnings. I started to fix them, usually
>> expliciting casting. Is it the way or is it better to avoid the
>> warning from the beginning, choosing the right signed or unsigned
>> type?
>>
>>
>
[toc] | [prev] | [next] | [standalone]
| From | Lawrence D’Oliveiro <ldo@nz.invalid> |
|---|---|
| Date | 2025-10-20 23:36 +0000 |
| Message-ID | <10d6h28$3id31$2@dont-email.me> |
| In reply to | #394594 |
On Mon, 20 Oct 2025 19:43:37 +0300, Michael S wrote: > I'd just point out that small negative numbers are FAR more common than > numbers in range [2**31..2**32-1]. Perhaps you mean *large* negative numbers? -1 is a larger number than -1000000.
[toc] | [prev] | [next] | [standalone]
| From | Kaz Kylheku <643-408-1753@kylheku.com> |
|---|---|
| Date | 2025-10-20 23:52 +0000 |
| Message-ID | <20251020163956.21@kylheku.com> |
| In reply to | #394606 |
On 2025-10-20, Lawrence D’Oliveiro <ldo@nz.invalid> wrote: > On Mon, 20 Oct 2025 19:43:37 +0300, Michael S wrote: > >> I'd just point out that small negative numbers are FAR more common than >> numbers in range [2**31..2**32-1]. > > Perhaps you mean *large* negative numbers? Oh look, twit is out of his depth again. There is definitely usage of "large" and "small" which implies magnitude: the more bits are required to encode the number, the larger it is. This semantics is particularly emphasized/clarified when followed by the word "negative". A "small negative" number is one closer to zero than a "large negative". If your bank account is -100,000.00, you have a large debt compared to someone with -100. There is little dispute that -100 is "greater than" -100,000. -- TXR Programming Language: http://nongnu.org/txr Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal Mastodon: @Kazinator@mstdn.ca
[toc] | [prev] | [next] | [standalone]
| From | Keith Thompson <Keith.S.Thompson+u@gmail.com> |
|---|---|
| Date | 2025-10-20 16:58 -0700 |
| Message-ID | <871pmxp1kz.fsf@example.invalid> |
| In reply to | #394606 |
Lawrence D’Oliveiro <ldo@nz.invalid> writes:
> On Mon, 20 Oct 2025 19:43:37 +0300, Michael S wrote:
>
>> I'd just point out that small negative numbers are FAR more common than
>> numbers in range [2**31..2**32-1].
>
> Perhaps you mean *large* negative numbers?
>
> -1 is a larger number than -1000000.
He clearly meant larger in magnitude. -1 is *greater* than -1000000,
but smaller in magnitude -- and "-1" is clearly smaller/shorter than
"-1000000".
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2025-10-20 20:03 +0200 |
| Message-ID | <10d5thm$3ciil$1@dont-email.me> |
| In reply to | #394592 |
On 20/10/2025 17:03, pozz wrote: > After many years programming in C language, I'm always unsure if it is > safer to use signed int or unsigned int. > > Of course there are situations where signed or unsigned is clearly > better. For example, if the values could assume negative values, signed > int is the only solution. If you are manipulating single bits (&, |, ^, > <<, >>), unsigned ints are your friends. > > What about other situations? For example, what do you use for the "i" > loop variable? > > I recently activated gcc -Wsign-conversion option on a codebase and > received a lot of warnings. I started to fix them, usually expliciting > casting. Is it the way or is it better to avoid the warning from the > beginning, choosing the right signed or unsigned type? > > Signed and unsigned types are equally safe. If you are sure you are within the ranges you know will work for the types you use, your code is safe. If you are not sure, you are unsafe. It doesn't matter if an overflow is undefined behaviour leading to a bug, or defined but unexpected behaviour leading to a bug. (Of course, if you are using the defined wrapping behaviour of unsigned types in a way that you know is correct for your program, then that is safe. All overflows for signed types are unsafe, as are all unexpected overflows of unsigned types.) Signed types can be more efficient in some circumstances, as they obey a number of useful mathematical rules that can be used for optimisation. Unsigned types - of the size of "unsigned int" or bigger - obey different mathematical identities that can occasionally be useful but often hinder optimisations. Beware assumptions about wrapping of unsigned types smaller than "unsigned int" - these promote to "int", and arithmetic is then done with "int" with UB overflow, before possibly being converted back to the smaller unsigned integer type. If your target has bigger registers than "int", sometimes code can be surprisingly inefficient for "unsigned int". If you have a 64-bit target and have an expression like "array[i++];" in a loop, it can be significantly less efficient if "i" is "unsigned int" because the compiler must assume that "i++" might wrap. If "i" is "int", or a 64-bit type (like "size_t" on such a target), there is no such issue. (It is not uncommon that "int_fast32_t" or "uint_fast32_t" will be faster than plain "int" or "unsigned int", because these will be 64-bit on 64-bit targets.) So very often, the efficient choice of type is "int" or "int_fastN_t" for code that might be used on 64-bit platforms. Size-explicit types are the best choice if your code has to run on smaller platforms, so that you can be sure your types are big enough. But if the compiler can determine the range of an unsigned variable (such as in a "for" loop where the start and end cases are known at compile-time), then unsigned types will be just as efficient. Comparisons between signed and unsigned types do turn up regularly, and sometimes casts are necessary if you want to enable warnings about these and can't reasonably pick the signedness for the two sides of the comparison. It's a question of style and preference whether you want to enable such warnings - most people do not, I think.
[toc] | [prev] | [next] | [standalone]
| From | Kaz Kylheku <643-408-1753@kylheku.com> |
|---|---|
| Date | 2025-10-20 20:09 +0000 |
| Message-ID | <20251020125218.835@kylheku.com> |
| In reply to | #394597 |
On 2025-10-20, David Brown <david.brown@hesbynett.no> wrote:
> On 20/10/2025 17:03, pozz wrote:
>> After many years programming in C language, I'm always unsure if it is
>> safer to use signed int or unsigned int.
>>
>> Of course there are situations where signed or unsigned is clearly
>> better. For example, if the values could assume negative values, signed
>> int is the only solution. If you are manipulating single bits (&, |, ^,
>> <<, >>), unsigned ints are your friends.
>>
>> What about other situations? For example, what do you use for the "i"
>> loop variable?
>>
>> I recently activated gcc -Wsign-conversion option on a codebase and
>> received a lot of warnings. I started to fix them, usually expliciting
>> casting. Is it the way or is it better to avoid the warning from the
>> beginning, choosing the right signed or unsigned type?
>>
>>
>
> Signed and unsigned types are equally safe. If you are sure you are
> within the ranges you know will work for the types you use, your code is
> safe. If you are not sure, you are unsafe.
Safe generally means that the language somehow protects from harm, not
that you protect yourself.
Correct code operating on correct inputs, using unsafe constructs,
is still called unsafe code.
However using unsigned types due to them being safe is often poorly
considered because if something goes wrong contrary to the programmer's
intent, there likely will be undefined behavior somewhere.
E.g. an array underflow using an unsigned index will not produce
integer overlow undefined behavior, but the access will go out of
bounds, which is undefined behavior.
There are bugs which play out without any undefined behavior:
the program calculates something contrary to its requirements,
but stays within the confines of the defined language.
The odds that by using unsigned numbers you will get only that type of
bug are low, and even if so, it is not a big comfort.
Signed numbers behave more like mathematical integers, in cases
when there is no overflow.
If a, b and c are small, non-negative quantities, you might be tempted
to make them unsigned. But if you do so, then you can no longer make
this derivation of inequalities:
a + b > c
c > a - b
Under the unsigned types, we cannot add -b to both sides of the
inequality, preserving its truth value, even if all the operands
are tiny numbers that fit into a single decimal digit!
If b happens to be greater than a, we get a huge value on the right
side that is now larger than c, not smaller.
Gratuitous use of unsigned types impairs our ability to
algebra to simplify code, due to the "cliff" at zero.
This is a nuanced topic where there isn't a one-type-fits-all answer,
but I gravitate toward signed; use of unsigned has to be justified in
some way.
When sizes are being calculated and they come from functions or
operators that produce size_t, then that tends to dictate unsigned.
If the quantities are large and can possibly overflow, there are
situations in which unsigned makes that simpler.
For instance if a and b are unsigned such that a + b can semantically
overflow (i.e. the result of the natural addition of a + b doesn't
fit into the type). It is simpler to detect: you can just do the
addition, and then test:
c = a + b;
when there is no overflow, it must be that (c >= a && c >= b)
so if either (c < a) or (c < b) is true, it overflowed.
This is significantly less verbose than a correct overflow test
for signed addition, which has to avoid doing the actual addition,
and has to be split into three cases: a and b have opposite
sign (always okay), a and b are both positive, and a and b are
both negative.
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @Kazinator@mstdn.ca
[toc] | [prev] | [next] | [standalone]
| From | rbowman <bowman@montana.com> |
|---|---|
| Date | 2025-10-21 01:43 +0000 |
| Message-ID | <mlo6pkFkdmiU2@mid.individual.net> |
| In reply to | #394600 |
On Mon, 20 Oct 2025 20:09:14 -0000 (UTC), Kaz Kylheku wrote: > This is a nuanced topic where there isn't a one-type-fits-all answer, > but I gravitate toward signed; use of unsigned has to be justified in > some way. It's more an illustration of legacy designs that didn't stand up well but a short was originally used in our code for object numbers. Gotta save bytes. Who ever thought there would be more than 32k objects? Changing it to unsigned short bought us time. Going to an int would have had repercussions because of those bytes a diligent programmer saved back in the '90s.
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2025-10-21 12:42 +0200 |
| Message-ID | <10d7o2d$3riat$1@dont-email.me> |
| In reply to | #394600 |
On 20/10/2025 22:09, Kaz Kylheku wrote: > On 2025-10-20, David Brown <david.brown@hesbynett.no> wrote: >> On 20/10/2025 17:03, pozz wrote: >>> After many years programming in C language, I'm always unsure if it is >>> safer to use signed int or unsigned int. >>> >>> Of course there are situations where signed or unsigned is clearly >>> better. For example, if the values could assume negative values, signed >>> int is the only solution. If you are manipulating single bits (&, |, ^, >>> <<, >>), unsigned ints are your friends. >>> >>> What about other situations? For example, what do you use for the "i" >>> loop variable? >>> >>> I recently activated gcc -Wsign-conversion option on a codebase and >>> received a lot of warnings. I started to fix them, usually expliciting >>> casting. Is it the way or is it better to avoid the warning from the >>> beginning, choosing the right signed or unsigned type? >>> >>> >> >> Signed and unsigned types are equally safe. If you are sure you are >> within the ranges you know will work for the types you use, your code is >> safe. If you are not sure, you are unsafe. > > Safe generally means that the language somehow protects from harm, not > that you protect yourself. No - "safe" means lower risk of harm, at least in /my/ book. It doesn't matter if it is something /you/ do, or something the language does, or something the tools do. (Ideally, of course, you want these all working together.) > > Correct code operating on correct inputs, using unsafe constructs, > is still called unsafe code. It will be called "unsafe code" by Rust salesmen, but not by software developers who work on safe code. "Safe code" is code used safely, it is not an inherent property of code constructs, types, or languages. All code constructs are unsafe if used incorrectly, while clear and well-understood code constructs are safe if used correctly. (Of course some languages, tools, and programming practices make it easier to write safe code, or harder to write unsafe code, or easier to tell the difference.) > > However using unsigned types due to them being safe is often poorly > considered because if something goes wrong contrary to the programmer's > intent, there likely will be undefined behavior somewhere. Exactly. Unsigned types are not somehow "safer" than signed types, just because signed types have UB on overflow. Don't overflow your signed types, then you have no UB. And if you overflow your unsigned types without that being an intentional and understood part of your code, you will at the very least get unexpected behaviour - a bug - and just like UB, there are no limits to how bad that can get. > > E.g. an array underflow using an unsigned index will not produce > integer overlow undefined behavior, but the access will go out of > bounds, which is undefined behavior. > Yes - bugs of all sorts often lead to UB sooner or later, even if the behaviour of the code is defined by the C language standards up to that point. > There are bugs which play out without any undefined behavior: > the program calculates something contrary to its requirements, > but stays within the confines of the defined language. > > The odds that by using unsigned numbers you will get only that type of > bug are low, and even if so, it is not a big comfort. > > Signed numbers behave more like mathematical integers, in cases > when there is no overflow. > > If a, b and c are small, non-negative quantities, you might be tempted > to make them unsigned. But if you do so, then you can no longer make > this derivation of inequalities: > > a + b > c > > c > a - b > > Under the unsigned types, we cannot add -b to both sides of the > inequality, preserving its truth value, even if all the operands > are tiny numbers that fit into a single decimal digit! > > If b happens to be greater than a, we get a huge value on the right > side that is now larger than c, not smaller. > > Gratuitous use of unsigned types impairs our ability to > algebra to simplify code, due to the "cliff" at zero. > Yes. > This is a nuanced topic where there isn't a one-type-fits-all answer, > but I gravitate toward signed; use of unsigned has to be justified in > some way. > > When sizes are being calculated and they come from functions or > operators that produce size_t, then that tends to dictate unsigned. > > If the quantities are large and can possibly overflow, there are > situations in which unsigned makes that simpler. > But normally, use of a bigger integer type makes the code significantly simpler and easier to get correct - and often more efficient. > For instance if a and b are unsigned such that a + b can semantically > overflow (i.e. the result of the natural addition of a + b doesn't > fit into the type). It is simpler to detect: you can just do the > addition, and then test: > > c = a + b; > > when there is no overflow, it must be that (c >= a && c >= b) > so if either (c < a) or (c < b) is true, it overflowed. > Or you use a bigger type and check simply and clearly for a result that is too big for your needs. Far too often, programmers go through reasoning like this and figure out what they see as "optimal" source code, then leave it in the source with no explanation as to what is going on. Aim to write code that does what it looks like it does - such as adds the two values correctly giving the mathematically correct result, then checks the range. Otherwise, good luck to the maintainer that changes the expression to "c = a + b + 1;". Or, with C23, use chk_add(). (Many compilers have extensions with the same effect, like __builtin_add_overflow, if you are happy using them.) > This is significantly less verbose than a correct overflow test > for signed addition, which has to avoid doing the actual addition, > and has to be split into three cases: a and b have opposite > sign (always okay), a and b are both positive, and a and b are > both negative. > Sure. But it is still significantly worse than using "long long int" (or "int_least64_t" if you prefer), or using ckd_add(). There are things in C23 that are somewhat controversial, but I think the checked integer operations are clearly a good standardisation of existing compiler-specific practice.
[toc] | [prev] | [next] | [standalone]
| From | James Kuyper <jameskuyper@alumni.caltech.edu> |
|---|---|
| Date | 2025-10-21 14:44 -0400 |
| Message-ID | <10d8kau$26bqh$3@dont-email.me> |
| In reply to | #394629 |
On 2025-10-21 06:42, David Brown wrote: > On 20/10/2025 22:09, Kaz Kylheku wrote: >> On 2025-10-20, David Brown <david.brown@hesbynett.no> wrote: >>> On 20/10/2025 17:03, pozz wrote: ...>> However using unsigned types due to them being safe is often poorly >> considered because if something goes wrong contrary to the programmer's >> intent, there likely will be undefined behavior somewhere. > > Exactly. Unsigned types are not somehow "safer" than signed types, just > because signed types have UB on overflow. Don't overflow your signed > types, then you have no UB. And if you overflow your unsigned types > without that being an intentional and understood part of your code, you > will at the very least get unexpected behaviour - a bug - and just like > UB, there are no limits to how bad that can get. No, there are limits on unexpected behavior: being unexpected, you might not know what they are, but it is still the case that the behavior starts out with having nothing more than an expression with an unexpected but valid value. That's pretty bad, and your code might make it worse, for example by promoting the unexpected value into undefined behavior. However, unless and until it actually does so, the behavior is somewhat more restricted than UB.
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2025-10-21 22:56 +0200 |
| Message-ID | <10d8s2q$6up0$1@dont-email.me> |
| In reply to | #394638 |
On 21/10/2025 20:44, James Kuyper wrote: > On 2025-10-21 06:42, David Brown wrote: >> On 20/10/2025 22:09, Kaz Kylheku wrote: >>> On 2025-10-20, David Brown <david.brown@hesbynett.no> wrote: >>>> On 20/10/2025 17:03, pozz wrote: > ...>> However using unsigned types due to them being safe is often poorly >>> considered because if something goes wrong contrary to the programmer's >>> intent, there likely will be undefined behavior somewhere. >> >> Exactly. Unsigned types are not somehow "safer" than signed types, just >> because signed types have UB on overflow. Don't overflow your signed >> types, then you have no UB. And if you overflow your unsigned types >> without that being an intentional and understood part of your code, you >> will at the very least get unexpected behaviour - a bug - and just like >> UB, there are no limits to how bad that can get. > > No, there are limits on unexpected behavior: being unexpected, you might > not know what they are, but it is still the case that the behavior > starts out with having nothing more than an expression with an > unexpected but valid value. That's pretty bad, and your code might make > it worse, for example by promoting the unexpected value into undefined > behavior. However, unless and until it actually does so, the behavior is > somewhat more restricted than UB. The effect of "unexpected behaviour" - something that has well-defined behaviour according to the C standard or the implementation, but was not what the programmer had intended or expected - is clear at the point it happens. Your unsigned arithmetic overflows in a defined and specified manner. But the knock-on effects are, in general, unpredictable - there are no specific limits for how bad things can get. It is not unlikely that you'll end up with "real" UB. In theory, real UB can lead to launching of nasal daemons, while unexpected behaviour, if it does not lead to real UB, cannot launch nasal daemons unless you have nasal daemon launch procedures in your program. In practice, real UB can more often lead to a quick crash and perhaps "nicer" bad behaviour (via OS memory protections and the like), while the unexpected behaviour can continue on, quietly causing future havoc and problems that are harder to find and debug. Either way, I think we can agree that bad things can happen!
[toc] | [prev] | [next] | [standalone]
| From | Keith Thompson <Keith.S.Thompson+u@gmail.com> |
|---|---|
| Date | 2025-10-20 14:48 -0700 |
| Message-ID | <87a51lp7lz.fsf@example.invalid> |
| In reply to | #394592 |
pozz <pozzugno@gmail.com> writes:
> After many years programming in C language, I'm always unsure if it is
> safer to use signed int or unsigned int.
>
> Of course there are situations where signed or unsigned is clearly
> better. For example, if the values could assume negative values,
> signed int is the only solution. If you are manipulating single bits
> (&, |, ^, <<, >>), unsigned ints are your friends.
>
> What about other situations? For example, what do you use for the "i"
> loop variable?
I usually use int (certainly for iterating over argc/argv), but
sometimes size_t. size_t is typically the most correct type for
representing sizes or counts of objects in memory, but int is a
bit easier to work with.
Both signed and unsigned types are (usually) used to model subranges
of the unbounded mathematical integers. If none of your operations
yields results outside the range of the type you're using, you're
safe -- but ensuring you don't stray outside that range can be easy
or difficult. If you're counting no more than a few thousand items,
int is fine. If you're counting bytes in a file or pennies in the
national debt, you have to think about just what range of values
you need to handle.
The thing about unsigned types is that they have a discontinuity at
0, which is much easier to run into than signed int's discontinuties
at INT_MIN and INT_MAX. Subtraction in particular can easily yield
mathematically incorrect results for unsigned types (unless your
problem domain actuall calls for modular arithmetic).
If you start with a value of type size_t, say from sizeof or
strlen(), it's probably best to stick with size_t for any derived
values. My vague impression is that most things that should use
unsigned types should use size_t (there are of course plenty of
exceptions).
> I recently activated gcc -Wsign-conversion option on a codebase and
> received a lot of warnings. I started to fix them, usually expliciting
> casting. Is it the way or is it better to avoid the warning from the
> beginning, choosing the right signed or unsigned type?
Here's the description of -Wconversion :
‘-Wsign-conversion’
Warn for implicit conversions that may change the sign of an
integer value, like assigning a signed integer expression to an
unsigned integer variable. An explicit cast silences the warning.
In C, this option is enabled also by ‘-Wconversion’.
If you're converting between different types, it's often (but by no
means always) best to pick one type and use it consistently. I'm
suspicious of most casts; if I need a conversion, I find that C's
implicit conversions usually do the right thing.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
[toc] | [prev] | [next] | [standalone]
| From | Keith Thompson <Keith.S.Thompson+u@gmail.com> |
|---|---|
| Date | 2025-10-20 17:13 -0700 |
| Message-ID | <87wm4pnmcw.fsf@example.invalid> |
| In reply to | #394602 |
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
[...]
> The thing about unsigned types is that they have a discontinuity at
> 0, which is much easier to run into than signed int's discontinuties
> at INT_MIN and INT_MAX. Subtraction in particular can easily yield
> mathematically incorrect results for unsigned types (unless your
> problem domain actuall calls for modular arithmetic).
One specific footgun enabled by unsigned types involves loops that count
down to zero. This :
for (int i = N; i >= 0; i --) {
// ...
}
is well behaved, but this :
for (size_t i = N; i >= 0; i --) {
// ...
}
is an infinite loop. A compiler might warn that `i >= 0` is always
true. You can work around this by checking the condition inside
the body of the loop, before the decrement that causes a wraparound :
for (size_t i = N; /* i >= 0 */; i --) {
// ...
if (i == 0) break;
}
But if your loop counts up, this isn't an issue.
"You too may be a big hero
Once you've learned to count backwards to zero."
-- Tom Lehrer, "Wernher Von Braun"
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
[toc] | [prev] | [next] | [standalone]
| From | Kaz Kylheku <643-408-1753@kylheku.com> |
|---|---|
| Date | 2025-10-21 01:45 +0000 |
| Message-ID | <20251020183259.925@kylheku.com> |
| In reply to | #394610 |
On 2025-10-21, Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
> Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
> [...]
>> The thing about unsigned types is that they have a discontinuity at
>> 0, which is much easier to run into than signed int's discontinuties
>> at INT_MIN and INT_MAX. Subtraction in particular can easily yield
>> mathematically incorrect results for unsigned types (unless your
>> problem domain actuall calls for modular arithmetic).
>
> One specific footgun enabled by unsigned types involves loops that count
> down to zero. This :
>
> for (int i = N; i >= 0; i --) {
> // ...
> }
>
> is well behaved, but this :
>
> for (size_t i = N; i >= 0; i --) {
> // ...
> }
We just have to translate the signed "i >= 0" into unsigned.
One way is to just directly translate the two's complement semantics
is doing, pretending that the high bit of the value is a sign bit:
// if the two's-complement-like "sign bit" is zero ...
(SIZE_MAX & (SIZE_MAX >> 1) & i) == 0
In a downard counting loop, we can just stop when we wrap around
to the highest value, so we get to use most of the range:
for (size_t i = N; i != SIZE_MAX; --i) // or (size_t) -1
(Note: I like to write --i when it's downward, just as a style; it
comes from stacks: stack[i++] = push; pop = stack[--i].)
The troublesome case is when N needs to start at SIZE_MAX!
But that troublesome case exists when counting upward also,
signed or unsigned.
Signed:
// We must break the loop before undefined i++:
for (int i = 0; i <= INT_MAX; i++)
// Need a bottom-loop break on SIZE_MAX or else infinite loop:
for (size_t i = 0; i <= SIZE_MAX; i++)
This is where BartC will chime in with how languages benefit from
built-in idioms for ranged loops that solve these problems under the
hood. It's a valid argument.
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @Kazinator@mstdn.ca
[toc] | [prev] | [next] | [standalone]
Page 1 of 2 [1] 2 Next page →
Back to top | Article view | comp.lang.c
csiph-web