Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

"There are other advantages to using unsigned types. For instance, it gives an explicit hint to the person reading the code about the range of the value."

This is, without doubt, the worst reason for using unsigned types, and it's the primary reason (IMHO) for the flaws in the C API that force you to use unsigned types unnecessarily. Unsigned types are not a documentation feature, and they are not merely an advert for an invariant; they are opting in to a subtly different arithmetic that most people are surprised by. It would be better to have a range-checked types, like Pascal, than to infect the program with unsigned arithmetic.

I find that most programs deal with values for their integer types with an absolute value of under 1000; about the only excuse for using an unsigned type, IMO, is when you must have access to that highest bit in a defined way (for safe shifting and bit-twiddling).



> they are opting in to a subtly different arithmetic that most people are surprised by

I think that's a "citation needed" moment there. It's true that any native integer type will strange if you go outside of its defined range. The only way to avoid that is to use a language that automatically converts to bignums behind the scene (Common Lisp, etc)

What I don't agree with is that this is something that "most people are surprised by" If anything, the word "unsigned" is a pretty good hint about what behavior you'll get.

And even when you play fast-and-loose with the rules, it usually turns out ok:

   unsigned a, b, c, d;
   a = b + (c - d);
even if d > c, this will do the expected thing on any 2's compliment architecture. Now, this will break if a and b were instead "unsigned long long". I think that case is fairly rare -- it's not a mistake I've seen commonly in real life (especially compared to the dangerous "botched range-check of a signed value" error)

But you are correct that it's not "merely an advert for an invariant" -- it's advertising that the compiler actually reads. It gives you better warnings (I've had plenty of bugs prevented by "comparison of signed and unsigned" warnings) It also allows the compiler to optimize better in some cases: compare the output of "foo % 16" with foo as signed and unsigned.

> It would be better to have a range-checked types, like Pascal

Adding runtime checks to arithmetic is the type of costs that are never going to be in C. This is no different than saying "C should have garbage collection" or "C should have RTTI". They're perfectly valid things to want in a language, but they're anathema to the niche that C holds in the modern world. With C I want "a + b" to compile down to one instruction -- no surprises.

And even if you DID do a range-check, what do you do if it fails? 1. Throw an exception? Sounds logical... oh wait, this is C there's no such thing as an exception 2. Clamp the value? Now you have behavior that is just as bizarre as an integer overflow 3. Crash? Not very friendly.. 4. Have a user-definable callback (i.e. like a signal) What is the chance that the programmer will be able to make meaningful recovery though?

There are, however, some additions to the C99 type system that I think would be useful.. for example C++11's strongly typed enum's are a good idea.

> I find that most programs deal with values for their integer types with an absolute value of under 1000

I find that most programs deal with values greater-than-or-equal-to zero.


I find that most programs deal with values greater-than-or-equal-to zero.

-1 is very frequently used as a sentinel value. For example, counting backwards through the elements of some container:

    for (i = count - 1; i >= 0; --i)
        /* body */;
I've had plenty of bugs prevented by "comparison of signed and unsigned" warnings

You wouldn't have had these warnings, much less needed to pay attention to them, if you hadn't had to use unsigned types in the first place.

This conversation is much like those around GC. It's impossible to convince people labouring under tyranny they've learned to love without them experiencing a free life first. You just can't communicate it with words.


> -1 is very frequently used as a sentinel value.

I actually think these sentinels work quite nicely with unsigned.

   unsigned element_num;
   static const unsigned NO_ELEMENT = (unsigned) -1;
Yes, you need a cast, but it's just one place. From then on you can use a nice constant for your sentinel.

Also, your sentinel is more range-check safe then it would have been if it were an int (the classic "if (x < sizeof(arr)) arr[x] = 1;" issue again)

> For example, counting backwards through the elements of some container:

Ok, that is a fair point. Tat type of loop is easy to mess up with an unsigned type. Worse, gcc will only warn you if you compile with -Wextra which not everybody does.

Actually implementing the loop in a safe manner isn't really that hard though, of course:

   if (count > 0) {
      unsigned i = count - 1;
      do {
         /* body */
      } while (i-- != 0);
   }
Couple extra lines, true. I don't think it loses much clarity.

> For example, counting backwards through the elements of some container:

You missed my point. I mean warnings caused by "oops, that's not the variable I meant to compare with there"

It's a similar story with "const". One of the great side-benefits of using "const" consistently is that suddenly you find that the compiler starts catching more of your dumb mistakes ("oh I thought this was called like func(dest,src) but it's actually func(src,dest)" The moral is that "more info to the compiler" translates to "compiler notices a larger percentage of your dumb mistakes"

> It's impossible to convince people labouring under tyranny they've learned to love without them experiencing a free life first.

Melodramatic much?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: