Skip to content
VLSI Mentor

C Part I · Module 2 · Values, Variables, Types and Representation

Signed and Unsigned Integers

Every integer type in C comes in two versions: one that can hold negative values and one that cannot. The obvious summary — unsigned means no minus sign — is true and almost useless, because it suggests the only difference is which values fit. The real difference is what happens at the edges, and there C is deliberately asymmetric: running off the end of an unsigned range is fully defined and does something specific, while running off the end of a signed range is undefined and does nothing you are entitled to predict. This chapter builds both halves of that asymmetry, shows the comparison trap that catches people who assume unsigned is simply the safer choice, and explains why the common advice to use unsigned for quantities that cannot be negative is not the rule it is usually presented as.

Foundation14 min readCPart IFundamentalsRepresentation

Part I · Module 2 · Chapter 2.6 · Signed and Unsigned Integers

1. Two Versions of Every Integer Type

Chapter 2.5 introduced C's family of integer types by the ranges they guarantee. Every one of them actually comes in two forms.

A signed type can hold negative values, zero, and positive values. A unsigned type holds zero and positive values only. You ask for the second by writing unsigned:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int  temperature = -5;       /* signed: negatives allowed   */
unsigned int count = 5;      /* unsigned: zero and up only  */

Plain int, short, long and long long are all signed already — writing signed int is legal and says nothing extra. So in practice the decision is: do I write unsigned or not?

The two forms have the same number of bits, which means they can record the same number of distinct patterns. What differs is which values those patterns are taken to mean. An unsigned type spends all of its patterns on non-negative values, so its maximum is roughly twice that of the corresponding signed type — and its minimum is 0.

That much is the summary everyone gives. Now the part that matters.

2. Unsigned Arithmetic Wraps, and C Says So

What happens if you go below zero in an unsigned type? Try it:

Azvya Education Pvt. Ltd.VLSI Mentor
going below zero
#include <stdio.h>

int main(void)
{
    unsigned int count = 0;

    count = count - 1;
    printf("count is %u\n", count);
    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
count is 4294967295

Two things about that result.

First, the value is the largest an unsigned int can hold on this machine. Subtracting one from zero did not give you −1; it gave you the top of the range. The values behave as though they were arranged in a ring rather than a line, so stepping below the bottom brings you round to the top.

Second — and this is the important part — that is not an accident, and it is not an implementation quirk. C defines it. Unsigned arithmetic is specified to work this way, which in Chapter 1.3's vocabulary makes it defined by the standard: every conforming implementation must behave like this.

So this is behaviour you may rely on. Code that deliberately uses wraparound is legitimate C, and you will meet it in Part II where it is occasionally exactly what you want.

3. Signed Overflow Is Undefined, and C Says Nothing

Now the same question for a signed type: what happens when you go past the largest int?

This chapter is not going to show you a program that does it, and the reason is the whole point.

Signed integer overflow is undefined behaviour. The standard imposes no requirements on what happens. Not "it wraps round like unsigned". Not "it gives you a negative". Not "it depends on the machine". There is simply no behaviour defined, which — as Chapter 1.3 established — means nothing about the program's conduct is predictable from that point, including parts that look unrelated.

Writing a demonstration program here would be actively harmful. It would produce some output, on this machine, with these options, and the only thing a reader could take from it is the belief that the result is worth knowing. It is not. Compilers routinely optimise on the assumption that signed overflow cannot occur, so the same source can behave differently at different optimisation levels — and that is permitted, not a bug in the compiler.

4. Why Unsigned Is Not Simply the Safer Choice

Here is the advice a beginner usually meets: if a value cannot be negative, make it unsigned. A count of items cannot be negative, so make it unsigned.

The reasoning sounds airtight and the conclusion is not reliable. Here is why:

Azvya Education Pvt. Ltd.VLSI Mentor
a comparison that is not what it looks like
#include <stdio.h>

int main(void)
{
    int signed_value = -1;
    unsigned int unsigned_value = 1;

    printf("is -1 less than 1? ");
    printf("%d\n", signed_value < unsigned_value);
    return 0;
}

Predict the answer before reading on. The output is:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
is -1 less than 1? 0

0 means false. C has just told you that −1 is not less than 1.

Nothing is broken. When a signed and an unsigned operand of the same size meet in one operation, C converts the signed one to unsigned before comparing. The -1 therefore does not take part in the comparison as −1; it takes part as the enormous value §2 produced. And that value is genuinely not less than 1.

This is not undefined behaviour — the conversion is precisely specified, and the result is the same everywhere. It is a trap rather than a bug in C: the rule is doing exactly what it says, and what it says is not what the code looks like it means. Chapter 3.12 covers the conversion rules generally; here you need only the one case above.

Your compiler will warn about it if asked:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]

which is yet another reason the options from Chapter 1.5 are not optional.

5. So Which Should You Use?

Given §4, the honest guidance is narrower than the usual advice:

Use signed types by default, including for quantities that happen to be non-negative. Signed arithmetic behaves the way your intuition expects near zero, subtraction cannot produce a surprise, and mixing signed values together has no conversion trap.

Use unsigned deliberately, when one of these is true:

  • You actually want wraparound — it is defined, and sometimes it is the behaviour you need.
  • You are working with a bit pattern rather than a quantity. This is the big one for this curriculum: masks, flags and register contents are patterns, not numbers, and unsigned is the right home for them. Part II is full of this.
  • An external specification — a file format, a protocol, a hardware register — says the field is unsigned. Then you match it.
  • You genuinely need the extra top half of the range and have thought about the edges.

Never choose unsigned merely because a value cannot be negative. That reasoning gives you no benefit and hands you §4's trap for free. The fact that a count cannot go below zero does not stop your code from computing something below zero on the way, and with unsigned the result is a very large number rather than a visible negative.

6. What You Should Be Able to Say Now

  1. Every integer type has a signed and an unsigned form; plain int, short, long and long long are already signed.
  2. Both forms have the same number of bits. Unsigned spends them all on non-negative values, so its maximum is roughly twice the signed maximum and its minimum is 0.
  3. Unsigned arithmetic wraps, and C defines it. Subtracting 1 from 0 gives the largest value of the type, everywhere, reliably.
  4. Signed overflow is undefined. C promises nothing, and the compiler is entitled to assume it cannot happen.
  5. Those two are not the same rule with different signs. One is a behaviour you may use; the other is a bug.
  6. When a signed and an unsigned operand of the same size meet, the signed one is converted to unsigned — which is why -1 < 1u is false.
  7. -Wsign-compare catches that comparison. %u prints unsigned values.
  8. Default to signed; choose unsigned for wraparound, for bit patterns, or to match an external specification — not merely because a value cannot be negative.

7. Exercise

Exercise 1 — predict, then run. What does each print?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
unsigned int a = 3;
unsigned int b = 5;
printf("%u\n", a - b);
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int c = 3;
int d = 5;
printf("%d\n", c - d);

Exercise 2 — classify the two ends. For each, say whether the behaviour is defined by the standard or undefined, and what that means for whether you may rely on the result: (a) An unsigned int holding its maximum value has 1 added to it. (b) An int holding its maximum value has 1 added to it. (c) An unsigned int holding 0 has 1 subtracted from it.

Exercise 3 — explain the false comparison. In one paragraph, explain to someone who has not read this chapter why -1 < 1 printed 0 in §4, without using the word "bug".

Exercise 4 — find the risk. This is a common shape. Say what could go wrong and under what condition:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
unsigned int items_total = 10;
unsigned int items_done = 12;
unsigned int items_left = items_total - items_done;

Exercise 5 — judge the advice. A colleague's code review comment says: "length can never be negative, so it should be unsigned." Using §5, give a reply that is neither simple agreement nor simple refusal.

Exercise 6 — choose the type. For each, say signed or unsigned and give a one-line reason: a temperature in degrees Celsius; a pattern selecting the low four bits of a byte; the number of retries remaining; a field defined by a hardware document as an unsigned 8-bit value.

8. Summary

  • Every integer type has both forms. Unsigned holds zero and up; the plain names are already signed.
  • Unsigned arithmetic wraps and C defines it. Zero minus one is the largest value of the type, guaranteed, everywhere.
  • Signed overflow is undefined. Nothing is promised, and compilers optimise on the assumption that it does not occur.
  • The asymmetry is the lesson. One end of the table is a tool; the other is a defect.
  • Mixing signed and unsigned converts the signed operand, which is why -1 < 1u is false. Specified, repeatable, and nothing like what the code appears to say.
  • Default to signed. Reach for unsigned when you want wraparound, when the thing is a bit pattern, or when an external specification says so.
  • Watch subtraction between unsigned values. That is where almost all of these bugs actually come from.

You now know which values each form can hold and how each behaves at its edges. What you still cannot do is look at a run of bits and say which negative number it records — §1 said the two forms use the same patterns for different values without ever saying how a negative value gets a pattern at all. That is the next chapter, and it is the one that finally connects Chapter 2.4's bit patterns to the negative numbers in this one.

Standards & specifications

Governing standard
ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)

Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C Programming curriculum.