94

When is it appropriate to use an unsigned variable over a signed one? What about in a for loop?

I hear a lot of opinions about this and I wanted to see if there was anything resembling a consensus.

for (unsigned int i = 0; i < someThing.length(); i++) {  
    SomeThing var = someThing.at(i);  
    // You get the idea.  
}

I know Java doesn't have unsigned values, and that must have been a concious decision on Sun Microsystems' part.

2
  • 3
    I found this helpful: codemines.blogspot.ca/2007/10/… Commented Aug 23, 2012 at 17:31
  • 1
    I think this question is quite opinion based. The presented code will run in both cases just fine, so you can use both. Except for performance reasons (no answer actually deals with performance so far) it's just personal taste. Commented Oct 9, 2014 at 9:13

6 Answers 6

86

I was glad to find a good conversation on this subject, as I hadn't really given it much thought before.

In summary, signed is a good general choice - even when you're dead sure all the numbers are positive - if you're going to do arithmetic on the variable (like in a typical for loop case).

unsigned starts to make more sense when:

  • You're going to do bitwise things like masks, or
  • You're desperate to to take advantage of the sign bit for that extra positive range .

Personally, I like signed because I don't trust myself to stay consistent and avoid mixing the two types (like the article warns against).

Sign up to request clarification or add additional context in comments.

6 Comments

Later in that thread, it's shown that unsigned is vastly superior for detecting overflow in untrusted inputs. Unfortunately, the proposed "answers" to the puzzle aren't all that great. Mine is template<size_t limit> bool range_check_sum( unsigned a, unsigned b ) { return (a < limit) && (b < limit - a); } If anyone has a similarly simple and straightforward answer using signed types, I'd love to see it.
Should mention using size_t for sizes and indices.
@BenVoigt can't you detect signed overflows with sanitizers, while unsigned overflow is completely valid behavior? see my answer
@qwr: There's a big difference between I detect the overflow (and choose my response) and the debugger detecting it. Sanitizers are toolchain specific and not useful in production.
@BenVoigt I disagree with sanitizers not being useful in production. The linux kernel has many of these sanitizers for good reason. Because they are not putting range_check_sum everywhere.
|
10

In your example above, when 'i' will always be positive and a higher range would be beneficial, unsigned would be useful. Like if you're using 'declare' statements, such as:

#declare BIT1 (unsigned int 1)
#declare BIT32 (unsigned int reallybignumber)

Especially when these values will never change.

However, if you're doing an accounting program where the people are irresponsible with their money and are constantly in the red, you will most definitely want to use 'signed'.

I do agree with saint though that a good rule of thumb is to use signed, which C actually defaults to, so you're covered.

Comments

10

I would think that if your business case dictates that a negative number is invalid, you would want to have an error shown or thrown.

With that in mind, I only just recently found out about unsigned integers while working on a project processing data in a binary file and storing the data into a database. I was purposely "corrupting" the binary data, and ended up getting negative values instead of an expected error. I found that even though the value converted, the value was not valid for my business case.
My program did not error, and I ended up getting wrong data into the database. It would have been better if I had used uint and had the program fail.

1 Comment

Thats exactly why you should not use uint. If you use int, you can check for invalid data. Then you can check for invalid (negative) values. If you use uint, your program will not fail, but just give a huge positive number.
8

C and C++ compilers will generate a warning when you compare signed and unsigned types; in your example code, you couldn't make your loop variable unsigned and have the compiler generate code without warnings (assuming said warnings were turned on).

Naturally, you're compiling with warnings turned all the way up, right?

And, have you considered compiling with "treat warnings as errors" to take it that one step further?

The downside with using signed numbers is that there's a temptation to overload them so that, for example, the values 0->n are the menu selection, and -1 means nothing's selected - rather than creating a class that has two variables, one to indicate if something is selected and another to store what that selection is. Before you know it, you're testing for negative one all over the place and the compiler is complaining about how you're wanting to compare the menu selection against the number of menu selections you have - but that's dangerous because they're different types. So don't do that.

Comments

7

size_t is often a good choice for this, or size_type if you're using an STL class.

2 Comments

Only when you're dealing with the size of something in bytes.
@mk12 Standard Library containers expose the size_type member for counts of elements, not just bytes. It should be used instead of std::size_t where available, but it's inaccurate to imply that anything starting with size_t can only mean bytes.
0

For debugging purposes, it's easier to catch overflow for signed integers using -ftrapv or -fsanitize=signed-integer-overflow in GCC and Clang (UBSan). C and C++ both define wrapping behavior for unsigned integers, so the sanitizer may not complain about it (UBSan's -fsanitize=integer still may complain), while signed overflow is always undefined behavior that the sanitizer can complain about (and the compiler is free to assume no signed overflow for optimization).

6 Comments

The debugger is rarely the environment where overflows are of most concern. Heartbleed didn't happen while a debugger was running. Even less useful when limited to only these compilers.
@BenVoigt this is not running in the debugger per se but as a separate sanitizer. Heartbleed was found with a fuzzer which is a completely different set of vulnerabilities. And GCC and Clang are the most popular compilers, with MSVC being a laggard in implementing features.
The most popular runtime environments are all embedded systems. Many of those use, or could use, GCC for cross-compilation, but it's not the same debugging experience. In most cases there's simply no place for ubsan to report its detections. And detections in production, to defend against malformed requests, are far more important than detection in a lab environment where a fuzzer is running. Terminating the process isn't a great outcome, it saves you from information disclosure or remote code execution but still is denial of service.
You want to be able to log the attempt, send back a response (if you choose) with an appropriate error code, and maybe detect repeated attempts and block further requests (ala fail2ban). The default action isn't sufficient. And by the time you start customizing a sanitizer to do what you need, your "it's easy as passing a compiler switch" argument is right out the window. Because now you not only need error handling in the places where overflow can occur, you need handshaking with the tool's runtime hooks.
The purpose of UBSan is to catch runtime UB and log it, not respond to it live. Both are important. Your point of view seems to be a server or program that has to recover and defend against malicious attacks. Mine is for, say a user or developer runs a program and wants to know why it crashed or returned an invalid result.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.