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-definedshort- at least 16 bitsint- 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),charsignedness). 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
- Use
intfor simple loop indices and small counts on hosted systems. - Use
<stdint.h>fixed-width types (uint32_t,int64_t) when sizes matter for protocols, file formats, or bit manipulation. - Use
size_tfor array indices and memory sizes; it is the correct unsigned type forsizeof. - Print with matching format:
%dforint,%uforunsigned,%ldforlong,%zuforsize_t,PRId32forint32_t. - Do not compare signed and unsigned values without thinking; integer promotion and conversion rules bite.
Check Yourself
- Why is
printf("%d\n", sizeof(int));wrong, and what is the correct form? - Is
signed char c = -1; unsigned int u = c;well-defined, and what value doesuget? - 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:
printf("%d\n", (int)((unsigned char)200 + (unsigned char)100));signed char c = 127; c = c + 1; printf("%d\n", c);unsigned char c = 255; c = c + 1; printf("%u\n", c);int x = -1; unsigned u = 1; printf("%d\n", x < u);printf("%zu %d\n", sizeof(long), (int)(sizeof(long)*CHAR_BIT));