Skip to main content

Primitive Types, Integer Widths, and Implementation-Defined Behavior

What This Concept Is

C's built-in types are narrower contracts than they look. The standard guarantees minimum widths, not exact ones:

  • char - at least 8 bits; signedness is implementation-defined
  • short - at least 16 bits
  • int - at least 16 bits (almost always 32 on modern desktop/Linux)
  • long - at least 32 bits (often 64 on Linux LP64, but 32 on Windows LLP64)
  • long long (C99) - at least 64 bits

If you need exact sizes, use the <stdint.h> fixed-width types (int32_t, uint64_t, ...), added in C99.

Integer operations do integer promotion first: any value of rank less than int is promoted to int (or unsigned int) before the operation. That is why (char)200 + (char)100 is computed in int space, not in char space.

Why It Matters Here

You will meet all three categories of "not fully defined" behavior in the first week:

  • implementation-defined - the implementation must document its choice (e.g., sizeof(int), char signedness). Portable code either queries the value via <limits.h>/<stdint.h> or documents the assumption.
  • unspecified - the standard lets the implementation choose from several options without documenting (e.g., evaluation order of function arguments).
  • undefined - the program is outside the C language's rules and the compiler may do anything (e.g., signed integer overflow, dereferencing a null pointer).

Portable C avoids all three where possible and documents the first when not.

Concrete Example

On a typical 64-bit Linux system:

#include <stdio.h>
#include <limits.h>
#include <stdint.h>
int main(void) {
printf("sizeof(int) = %zu\n", sizeof(int)); // 4
printf("sizeof(long) = %zu\n", sizeof(long)); // 8 on Linux, 4 on Windows
printf("INT_MAX = %d\n", INT_MAX); // 2147483647
printf("sizeof(int32_t) = %zu\n", sizeof(int32_t)); // 4 everywhere

unsigned char a = 200, b = 100;
printf("a + b = %d\n", a + b); /* prints 300, not 44: integer promotion */

int i = INT_MAX;
// i = i + 1; /* undefined behavior: signed overflow */

unsigned int u = UINT_MAX;
u = u + 1; /* defined: unsigned wraps to 0 */
printf("u = %u\n", u);
return 0;
}

Common Confusion / Misconception

"char is signed." In ISO C, char is a distinct type whose signedness is implementation-defined. On x86-64 Linux it is signed; on many ARM systems it is unsigned. If you care, write signed char or unsigned char explicitly.

"Overflow wraps around." Only for unsigned types, which are defined to wrap modulo 2^N. Signed integer overflow is undefined behavior, and modern compilers exploit that assumption (e.g., deleting if (x + 1 < x) checks).

How To Use It

  1. Use int for simple loop indices and small counts on hosted systems.
  2. Use <stdint.h> fixed-width types (uint32_t, int64_t) when sizes matter for protocols, file formats, or bit manipulation.
  3. Use size_t for array indices and memory sizes; it is the correct unsigned type for sizeof.
  4. Print with matching format: %d for int, %u for unsigned, %ld for long, %zu for size_t, PRId32 for int32_t.
  5. Do not compare signed and unsigned values without thinking; integer promotion and conversion rules bite.

Check Yourself

  1. Why is printf("%d\n", sizeof(int)); wrong, and what is the correct form?
  2. Is signed char c = -1; unsigned int u = c; well-defined, and what value does u get?
  3. Why might for (unsigned i = n; i >= 0; i--) be an infinite loop?

Mini Drill or Application

Predict the output of each snippet; then compile and compare:

  1. printf("%d\n", (int)((unsigned char)200 + (unsigned char)100));
  2. signed char c = 127; c = c + 1; printf("%d\n", c);
  3. unsigned char c = 255; c = c + 1; printf("%u\n", c);
  4. int x = -1; unsigned u = 1; printf("%d\n", x < u);
  5. printf("%zu %d\n", sizeof(long), (int)(sizeof(long)*CHAR_BIT));

Read This Only If Stuck