News for hackers. Stuff that matters.
Re:Pitfalls in C and C++: Unsigned Types (2011) by Mindless2112 on Wednesday September 25, 2013 @02:32PM (#6446387)

  > “Unsigned has a bigger range.”
  > Only a bit bigger.
So... "only" twice as big.

I'm not particularly fond of the trend toward avoiding unsigned integers (in particular the lack of unsigned types in Java and array lengths being signed in C#).

A good programmer should be capable of determining whether to use a signed or unsigned type; if it's not easy to decide, that indicates a design flaw to me.

Re:Pitfalls in C and C++: Unsigned Types (2011) by limmeau on Wednesday September 25, 2013 @02:40PM (#6446432)
So signeds are better because they overflow differently?

If you program C like there's no overflow, it doesn't matter much whether you mis-index the next array with 4294967295 or -1. There's no substitute for overflow-vigilance, and particularly not rules like "such-and-such type is bad and such-and-such type is better".

(BTW: an ex-boss of mine claimed that signed ints were evil because because and insisted on using unsigned for everything, but that's a different story, and I'm glad he's an ex-boss)

Re:Pitfalls in C and C++: Unsigned Types (2011) by oleganza on Wednesday September 25, 2013 @02:42PM (#6446444)
OP means to say that when dealing with small enough numbers, unsigned integer may overflow, while the signed one won't. With huge numbers they both overflow, but we usually don't have them.
Re:Pitfalls in C and C++: Unsigned Types (2011) by guerrilla on Wednesday September 25, 2013 @03:25PM (#6446722)
Seriously. This seems totally basic to me. If someone wants to program in C, vigilance for case coverage of any kind is essential.
Re:Pitfalls in C and C++: Unsigned Types (2011) by pslam on Wednesday September 25, 2013 @02:40PM (#6446431)
I think the opposite - signed types are a big problem in statically typed languages and the cause of countless bugs I've had to deal with (in other people's code) for most of my career.

I think most languages would benefit from unsigned types being the default, and arithmetic overflow being a hard error unless otherwise decorated. Signedness and lenient overflow promote laziness. Array indexes don't make sense as signed, yet most people prefer to iterate arrays with a signed type, e.g most commonly:

  for (int i = 0; i < 10; ++i) buf[i] = 0;
Far too many people rely on signed integer overflow working as you expect the underlying machine to handle it - but that's not what the C spec says and not how a compiler handles it either.

There are countless security issues I've had to fix related to signed types in what is supposed to be secure code. These would not have occurred if the author was forced to use an unsigned type, and had to consider the extreme limits of the values it can take on. Subtle things such as the addressable limit of memory being naturally unsigned, but buffer sizes being passed as signed, can cause easy exploits, and are stupidly commonplace.

Re:Pitfalls in C and C++: Unsigned Types (2011) by cgore on Wednesday September 25, 2013 @02:46PM (#6446474)
Actually, array indices do make sense as negative. For example, you might want an array going from a[-500] to a[700], without having to adjust the index all the time. It is much nicer if the array does it for you. Fortran lets you change the starting index if I remember correctly.

Most commonly you'll actually want to go positive, and have an array going from a[1970] to a[2013] or something similar.

Re:Pitfalls in C and C++: Unsigned Types (2011) by frenchy on Wednesday September 25, 2013 @02:58PM (#6446548)
Perhaps, though from a language design standpoint I think that adds unnecessary complexity. Either way, we're talking about C here, and in that context signed integers don't make sense as array indexes.
Re:Pitfalls in C and C++: Unsigned Types (2011) by dllthomas on Wednesday September 25, 2013 @03:05PM (#6446603)
The fact that we're talking about C doesn't really make a difference, if you genuinely desire negative indices:

    int array[FOO];
    int *shifted_array = array + FOO/2;
Re:Pitfalls in C and C++: Unsigned Types (2011) by cgore on Wednesday September 25, 2013 @03:11PM (#6446638)
That's actually a nice trick. I just hope the compiler doesn't complain about it. I'll have to try that sometime.

EDIT: just tried it. The following works:

    #include <stdio.h>
    int main(void)
    {
        int array[100];
        int *shifted_array = array - 1980;
        for (int i = 0; i < 100; i++)
            array[i] = i;
        for (int j = 1980; j < 2014; j++)
            printf("shifted_array[%d] = %d\n", j, shifted_array[j]);
        return 0;
    }
Re:Pitfalls in C and C++: Unsigned Types (2011) by dllthomas on Wednesday September 25, 2013 @03:44PM (#6446837)
Yeah, there's a couple cases where this kind if thing cleans things up; use it with caution though.

More amusing, but less useful outside the IOCCC:

The C subscripting operator performs an basically syntactic transformation, turning a[i] into (*(a + i)). This means that, counter-intuitively (if you're thinking of [] as a lookup) the construct i[a] works too because plus is commutative.

Re:Pitfalls in C and C++: Unsigned Types (2011) by elbee on Wednesday September 25, 2013 @02:59PM (#6446557)
On the other hand unsigned types are a huge pain if you want to iterate through an array backwards because you have to use subtraction. A lot of people end up with something like this:

  unsigned int i = strlen(s) - 1;
  for (; i >= 0; --i) { // BUGBUG
      if (s[i] == '.') {
          break;
      }
  }
(Yes, you can make it work, but it is very error-prone when people try).
Re:Pitfalls in C and C++: Unsigned Types (2011) by pslam on Wednesday September 25, 2013 @03:08PM (#6446617)
Simple transformation:

  for (unsigned i = strlen(s); i > 0; --i) {
    if (s[i - 1] == '.') break;
  }
Easy to see that s[i-1] does not underflow the array due to the loop invariant i>0. It's usually easy to convert signed iteration code to unsigned and when I see this, I can tell the author spent the time to consider what happens at the limits of their inputs.
Re:Pitfalls in C and C++: Unsigned Types (2011) by beagle3 on Thursday September 26, 2013 @12:34AM (#6448976)
gcc has been complaining about that one for the last 15 years or so, saying "condition is always true i >= 0".

Some errors are more subtle and not flagged by compilers, but many are.

Re:Pitfalls in C and C++: Unsigned Types (2011) by oleganza on Wednesday September 25, 2013 @02:46PM (#6446470)
buf[-1] is equally incorrect as buf[4294967295], so int being signed or unsigned does not make any difference to the correctness of your loop.
Re:Pitfalls in C and C++: Unsigned Types (2011) by dllthomas on Wednesday September 25, 2013 @02:55PM (#6446529)
Not so. buf[-1] will step on mapped memory that is probably something else important, and you won't find out about it until later, which is hard to debug and more likely disastrous (if there's room for disaster in the use of the software). buf[4294967295] will try to use memory that is almost certainly unmapped and unavailable, and will segfault immediately - right on the line that caused the problem - and won't take any potentially disastrous actions.
Re:Pitfalls in C and C++: Unsigned Types (2011) by pslam on Wednesday September 25, 2013 @02:57PM (#6446545)
buf[4294967295] is perfectly valid and expected on platforms where size_t is 64 bits and int is 32 bits. buf[-1] is almost always a bug, and would be useful to default (with overrides) as such.
Re:Pitfalls in C and C++: Unsigned Types (2011) by dllthomas on Wednesday September 25, 2013 @03:13PM (#6446644)
if buf[4295967295] is valid, then you should almost certainly be indexing with something bigger than 32 bits, and the max of your index is again likely to be invalid (and, as I've mentioned elsewhere, more usefully invalid than -1).
Re:Pitfalls in C and C++: Unsigned Types (2011) by dllthomas on Thursday September 26, 2013 @03:14AM (#6449349)
On reflection, using something larger doesn't help, since if sizeof(void*) == sizeof(i) then a[(__typeof(i))-1] will almost certainly refer to the same address whether i is signed or unsigned.
Re:Pitfalls in C and C++: Unsigned Types (2011) by betterunix on Wednesday September 25, 2013 @02:57PM (#6446542)
The problem is not unsigned types. The problem is overflow and underflow conditions not being reported.

Compare this to, say, the "declarations-as-assertions" extension in CMUCL/SBCL: when you declare an unsigned type, underflow conditions will be reported:

  * (defun test (x) 
	(the (integer 0) (1- x))
	)
  * (test 0)

  debugger invoked on a TYPE-ERROR in thread
  #<THREAD "main thread" RUNNING {1002B2AF23}>:
    The value -1 is not of type UNSIGNED-BYTE.
Re:Pitfalls in C and C++: Unsigned Types (2011) by dllthomas on Wednesday September 25, 2013 @03:10PM (#6446624)
Sure. Of course, at the same time sometimes overflow and undeflow is fine and should be ignored (when treating unsigned ints as the modular ring they actually are). This is of course not a criticism of CL, which I understand does not check these by default and so is clearly capable of not checking - just a note to anyone who might take more from your comment than was there.
Re:Pitfalls in C and C++: Unsigned Types (2011) by betterunix on Wednesday September 25, 2013 @05:06PM (#6447288)
Actually, CL goes a step further, which I like: by default, integer arithmetic is arbitrary precision. There are no overflows, unless you count running out of RAM to store your numbers as an overflow. Modular arithmetic must be explicitly specified, leaving no room for surprises:

  (mod (+ x y) N)
The reason I prefer this behavior to C's is that it is more natural. Most of the time when we think of integer arithmetic, we do not assume that it is modulo N; we assume things like " x - 1 < x " will always be true. Forcing unexpected semantics on programmers is a recipe for bugs, and integer underflow/overflow bugs are not uncommon in C programs.
Re:Pitfalls in C and C++: Unsigned Types (2011) by dllthomas on Wednesday September 25, 2013 @05:13PM (#6447327)
I think that'd probably be inappropriate for C's use cases, but I think generally it's a good move. I do like encoding the modular behavior in the type, FWIW; specifying a mod manually everywhere it goes leads to unreadable code. I agree, however, that it should probably not be the default type.
Re:Pitfalls in C and C++: Unsigned Types (2011) by betterunix on Wednesday September 25, 2013 @04:39PM (#6447146)
The way I see it, if you want arithmetic modulo N, you should be explicit. For example, in CL:

[Edit: Just noticed this got posted, but someone replied. The correct version of this is above.]

Re:Pitfalls in C and C++: Unsigned Types (2011) by dllthomas on Wednesday September 25, 2013 @04:42PM (#6447159)
Sure, I don't disagree. I'm just saying that's a legitimate use case that should be supported somehow.
Re:Pitfalls in C and C++: Unsigned Types (2011) by detrino on Wednesday September 25, 2013 @06:15PM (#6447676)
I think a lot of the pitfalls with unsigned types relates to the for loop construct being biased towards counting up. Consider the following code, the down for loop is really ugly and unintuitive, but the down while loop is symmetrical to the up loop. Maybe C/C++ need a counterpart to the for loop where the loop body executes last?

    #include <iostream>

    int main()
    {
        unsigned I;

        // 0 to 4, for loop
        for (I = 0; I != 5; ++I) std::cout << I << "\n";
        std::cout << "\n";

        // 4 to 0, for loop
        for (I = 5; I-- != 0;) std::cout << I << "\n";
        std::cout << "\n";

        // 0 to 4, while loop
        I = 0;
        while (I != 5)
        {
            std::cout << I << "\n";
            ++I;
        }
        std::cout << "\n";

        // 4 to 0, while loop
        I = 5;
        while (I != 0)
        {
            --I;
            std::cout << I << "\n";
        }
        std::cout << "\n";
    }
Re:Pitfalls in C and C++: Unsigned Types (2011) by ggchappell on Wednesday September 25, 2013 @11:45PM (#6448828)
When the rubber meets the road, and I need to write some actual code, I guess I'm not sure what is being recommended here. Say I need to iterate through the indices of a vector--his "strongest counterargument". He suggests "casting the unsigneds to signed integers". Am I supposed to do this?

  for (ptrdiff_t i = 0; i != ptrdiff_t(v.size()); ++i)
I think the real answer here is for something like Python's itertools to be in the C++ Standard Library, so that we never need to do anything remotely like the code above. But failing that, it isn't clear to me what the "best practices" version of the above would be.
Re:Pitfalls in C and C++: Unsigned Types (2011) by dbrower on Thursday September 26, 2013 @10:36AM (#6451018)
I take this as an overgeneralization from the bugs the original author runs into most often, which may be domain specific -- overflow in signal processing being endemic to the problem.

In many other cases, one of the common bugs is unexpected sign extension when a type is promoted to a larger one -- the most common being extension of a signed character to an integer, which can result in some wildly negative values.

Those who have been hit by that frequently will prefer unsigned types.

As with most things, your mileage may vary.

-dB

Re:Pitfalls in C and C++: Unsigned Types (2011) by dllthomas on Wednesday September 25, 2013 @03:02PM (#6446579)
Use unsigned when indexing arrays, for two reasons:

First, arr[(unsigned)-1] will segfault while arr[-1] will likely cause harder to debug non-local problems.

Second, mod in C and C++ is broken(ish) on signed values: negative % positive returns a negative number. This means in order to use it as an index you need to check the value and add the table size post-mod if you want to wrap an index into an array (as you might for a hash table or ring buffer).

Re:Pitfalls in C and C++: Unsigned Types (2011) by Someone on Wednesday September 25, 2013 @06:56PM (#6447865)
I don't see why arr[(unsigned)-1] is guaranteed to segfault. Care to explain?

Isn't it equivalent to

  * (arr + (unsigned) -1)
, which should be equivalent to

  * (arr + UINT_MAX)
On typical PC hardware with sizeof(int) == sizeof(char *) that looks like a undefined behavior case to me (maybe barring border conditions; I don't feel like thinking of all edge cases). Most C compilers choose the fastest possible path for undefined behavior cases, so I would expect this to (typically. Again, there will be border cases) be equivalent to arr[-1].

(See also http://stackoverflow.com/questions/2578455/pointer-arithmeti...)

Re:Pitfalls in C and C++: Unsigned Types (2011) by dllthomas on Thursday September 26, 2013 @03:09AM (#6449321)
"Typical PC hardware", at least in my recent experience, has sizeof(int) == 4, sizeof(char*) == 8 and so the reason they're different should be apparent. I believe you are correct that there is no difference when they are equal.
Re:Pitfalls in C and C++: Unsigned Types (2011) by duaneb on Wednesday September 25, 2013 @03:17PM (#6446673)
This is ok in general, but IMHO silently failing code (like a break condition that never happens because of overflow) is far worse than getting your arithmetic right. Getting it wrong implies a clear bug, whereas a seemingly bugless loop might be harder to identify as the source of the error.
Re:Pitfalls in C and C++: Unsigned Types (2011) by dllthomas on Wednesday September 25, 2013 @03:40PM (#6446817)
I think you either misread me, or I'm misreading you. I'm advocating the method that fails immediately (unsigned, so an underflow hits unmapped memory immediately and segfaults).
Re:Pitfalls in C and C++: Unsigned Types (2011) by codehero on Wednesday September 25, 2013 @02:39PM (#6446427)
I've definitely been bitten by this kind of bug before, but I did not come to this author's conclusion. What I learned is that each step of a calculation has lower and upper bounds, regardless of int type. You always make sure your inputs, intermediates and products fall within these bounds. Consequently, the author's advice becomes irrelevant.
Re:Pitfalls in C and C++: Unsigned Types (2011) by dnautics on Wednesday September 25, 2013 @02:31PM (#6446382)
if you are implementing fixed point arithmetic, using uints is probably a good idea, too.
Re:Pitfalls in C and C++: Unsigned Types (2011) by deletes on Wednesday September 25, 2013 @02:42PM (#6446443)
How are you going to get negative coordinates then?