|$ curl https://forge-ai.dev/api/markdown?path=docs/c/type-system
$cat docs/c-—-type-system.md
updated Recently·40 min read·published

C — Type System

CTypesCastingAdvancedAdvanced🎯Free Tools
Type System Overview

C has a weak, static, nominal type system. Types are checked at compile time, there is no runtime type information, and the compiler permits many implicit conversions that can silently produce incorrect results. Understanding the type system is essential to writing correct, portable C code — and to avoiding the class of bugs that arise from implicit conversions and undefined behavior.

types_overview.c
C
1#include <stdio.h>
2#include <limits.h>
3#include <float.h>
4
5int main(void) {
6 /* Fundamental integer types — guaranteed minimum ranges */
7 printf("char: %zu bytes (min %d, max %d)\n",
8 sizeof(char), CHAR_MIN, CHAR_MAX);
9 printf("short: %zu bytes (min %d, max %d)\n",
10 sizeof(short), SHRT_MIN, SHRT_MAX);
11 printf("int: %zu bytes (min %d, max %d)\n",
12 sizeof(int), INT_MIN, INT_MAX);
13 printf("long: %zu bytes (min %ld, max %ld)\n",
14 sizeof(long), LONG_MIN, LONG_MAX);
15 printf("long long: %zu bytes (min %lld, max %lld)\n",
16 sizeof(long long), LLONG_MIN, LLONG_MAX);
17
18 /* Floating-point types */
19 printf("float: %zu bytes (digits %d)\n",
20 sizeof(float), FLT_DIG);
21 printf("double: %zu bytes (digits %d)\n",
22 sizeof(double), DBL_DIG);
23 printf("long double: %zu bytes (digits %d)\n",
24 sizeof(long double), LDBL_DIG);
25
26 return 0;
27}
📝

note

C does not define exact sizes for most types — only minimum ranges. An int must hold at least −32767 to 32767, but on most modern platforms it is 32 bits. Always use <stdint.h> types when exact widths matter.
Fundamental Types

The C standard defines five signed integer types, three floating-point types, and several qualifier-modified variants. Each has specific guarantees about range and precision.

fundamental_types.c
C
1#include <stdio.h>
2#include <stdint.h>
3#include <inttypes.h>
4#include <limits.h>
5
6int main(void) {
7 /* Exact-width integer types from stdint.h */
8 int8_t a = -128;
9 int16_t b = -32768;
10 int32_t c = -2147483648;
11 int64_t d = -9223372036854775807LL - 1;
12
13 uint8_t ua = 255;
14 uint16_t ub = 65535;
15 uint32_t uc = 4294967295U;
16 uint64_t ud = 18446744073709551615ULL;
17
18 /* PRIx64 macro for portable printf of 64-bit values */
19 printf("int64_t min: %" PRId64 "\n", d);
20 printf("uint64_t max: %" PRIu64 "\n", ud);
21 printf("hex: %" PRIx64 "\n", ud);
22
23 /* Minimum-width types */
24 int_least8_t la = -128;
25 int_least32_t lb = -2147483648;
26 int_fast64_t lc = 9223372036854775807LL;
27
28 /* Pointer-width types */
29 intptr_t ptr_val = (intptr_t)&a;
30 uintptr_t uptr_val = (uintptr_t)&a;
31 printf("pointer as hex: %#" PRIxPTR "\n", uptr_val);
32
33 return 0;
34}

info

Use int32_t and uint64_t when you need guaranteed widths. Use int_fast64_t when you want the fastest type at least that wide. Use size_t for sizes and indices.
_Bool and Boolean Types (C99)

C99 introduced _Bool, a native boolean type that stores 0 or 1. The <stdbool.h> header provides the bool, true, and false macros for readability.

bool_type.c
C
1#include <stdio.h>
2#include <stdbool.h>
3
4int main(void) {
5 _Bool raw_bool = 1; /* Native C99 boolean */
6 bool nice_bool = true; /* Macro from stdbool.h */
7
8 /* Any nonzero value converts to 1 (true) */
9 _Bool b1 = 42; /* b1 == 1 */
10 _Bool b2 = -1; /* b2 == 1 */
11 _Bool b3 = 0.5; /* b3 == 1 */
12 _Bool b4 = 0; /* b4 == 0 */
13
14 printf("42 -> _Bool: %d\n", b1);
15 printf("-1 -> _Bool: %d\n", b2);
16 printf("0.5 -> _Bool: %d\n", b3);
17
18 /* _Bool does NOT flip like a logical NOT:
19 _Bool b5 = !0; // b5 == 1
20 _Bool b6 = !42; // b6 == 0 (because !42 is 0) */
21
22 /* Use in conditions */
23 if (nice_bool && b1) {
24 printf("Both are true\n");
25 }
26
27 return 0;
28}
Complex and Imaginary Types (C99)

C99 added native complex number support via _Complex. The <complex.h> header provides macros and functions for working with complex arithmetic.

complex_types.c
C
1#include <stdio.h>
2#include <complex.h>
3#include <math.h>
4
5int main(void) {
6 /* Declare complex numbers */
7 double complex z1 = 3.0 + 4.0 * I;
8 double complex z2 = 1.0 - 2.0 * I;
9
10 /* Arithmetic */
11 double complex sum = z1 + z2;
12 double complex product = z1 * z2;
13
14 printf("z1 = %.1f + %.1fi\n", creal(z1), cimag(z1));
15 printf("z2 = %.1f + %.1fi\n", creal(z2), cimag(z2));
16 printf("sum = %.1f + %.1fi\n", creal(sum), cimag(sum));
17 printf("product = %.1f + %.1fi\n", creal(product), cimag(product));
18
19 /* Magnitude and phase */
20 double mag = cabs(z1);
21 double phase = carg(z1);
22 printf("|z1| = %.4f\n", mag);
23 printf("arg(z1) = %.4f rad\n", phase);
24
25 /* Conjugate */
26 double complex conj = conj(z1);
27 printf("conj(z1) = %.1f + %.1fi\n", creal(conj), cimag(conj));
28
29 /* Imaginary type (optional in C99) */
30 double imaginary img = 3.0 * I;
31 printf("imaginary: %.1f + %.1fi\n", 0.0, cimag(img));
32
33 return 0;
34}

warning

Complex arithmetic may not be supported on all platforms. Some embedded compilers do not implement _Complex. Check your compiler documentation.
Unsigned Integer Arithmetic

Unsigned integer arithmetic in C is defined to wrap around using modular arithmetic. There is no overflow — values wrap at UINT_MAX + 1. This is well-defined behavior, unlike signed overflow.

unsigned_arith.c
C
1#include <stdio.h>
2#include <stdint.h>
3
4int main(void) {
5 uint8_t u = 255;
6 printf("uint8_t max: %u\n", u); /* 255 */
7 u += 1;
8 printf("after +1: %u\n", u); /* 0 — wraps around */
9
10 uint32_t big = 0;
11 big -= 1;
12 printf("uint32_t 0 - 1: %u\n", big); /* 4294967295 */
13
14 /* Modular division: no divide-by-zero undefined behavior
15 when divisor is 0 — result is undefined per standard,
16 but many implementations define it. Use explicit checks. */
17 uint32_t x = 10;
18 uint32_t y = 0;
19 if (y != 0) {
20 printf("%u / %u = %u\n", x, y, x / y);
21 } else {
22 printf("Division by zero avoided\n");
23 }
24
25 /* Unsigned subtraction used as array index — dangerous */
26 size_t len = 5;
27 size_t idx = 3;
28 printf("len - idx = %zu\n", len - idx); /* 2 */
29
30 idx = 10;
31 /* This wraps to a huge number! len - idx = SIZE_MAX - 4 */
32 printf("len - idx (wrapped): %zu\n", len - idx);
33
34 return 0;
35}

warning

Never use unsigned subtraction where the result could be negative. The result wraps to a massive positive value, which is usually a bug — not a feature.
Signed Integer Overflow — Undefined Behavior

Signed integer overflow is undefined behavior in C. The compiler may assume it never happens and optimize accordingly. This is one of the most common sources of subtle bugs in C programs.

signed_overflow.c
C
1#include <stdio.h>
2#include <limits.h>
3
4int main(void) {
5 int x = INT_MAX;
6
7 /* UNDEFINED BEHAVIOR — do NOT do this */
8 /* int y = x + 1; */
9
10 /* Safe alternative: check before arithmetic */
11 if (x < INT_MAX - 1) {
12 int y = x + 1;
13 printf("y = %d\n", y);
14 } else {
15 printf("Addition would overflow\n");
16 }
17
18 /* Compiler may optimize away entire branches based on UB.
19 Example: this loop may never terminate in optimized builds: */
20 int counter = 0;
21 for (int i = 1; i > 0; i *= 2) {
22 counter++;
23 if (counter > 100) break; /* Safety valve */
24 }
25 printf("Counter: %d\n", counter);
26
27 /* Use saturating arithmetic when overflow is expected */
28 int a = INT_MAX;
29 int b = 1;
30 int result = (a > INT_MAX - b) ? INT_MAX : a + b;
31 printf("Saturated: %d\n", result);
32
33 return 0;
34}

best practice

Always validate that arithmetic will not overflow before performing it. Use safe math patterns: if (a > MAX - b) before a + b.
Implicit Type Conversions

C performs two key implicit conversions: integer promotion (smaller types promoted to int in expressions) and usual arithmetic conversions (operands of different types reconciled to a common type).

implicit_conversions.c
C
1#include <stdio.h>
2
3int main(void) {
4 /* Integer promotion: char/short promoted to int in expressions */
5 char a = 100;
6 char b = 50;
7 int sum = a + b; /* a and b promoted to int before addition */
8 printf("char + char = int: %d\n", sum);
9
10 /* Without promotion, 100 + 50 = 150 fits in char,
11 but the result is always int */
12 char c = a + b; /* Implicit narrowing — potential data loss */
13 printf("char result: %d\n", c);
14
15 /* Usual arithmetic conversions: when types differ */
16 int i = 10;
17 double d = 3.14;
18 double result = i + d; /* i converted to double */
19 printf("int + double = %.2f\n", result);
20
21 /* Float vs int: int converted to float */
22 float f = 2.0f;
23 int n = 3;
24 float fr = f + n; /* n converted to float */
25 printf("float + int = %.2f\n", fr);
26
27 /* long vs int: int converted to long */
28 long l = 1000000L;
29 int m = 42;
30 long lr = l + m; /* m converted to long */
31 printf("long + int = %ld\n", lr);
32
33 /* Unsigned vs signed — dangerous! */
34 unsigned int u = 3;
35 int s = -5;
36 /* s converted to unsigned — becomes huge positive number! */
37 unsigned int dangerous = u + (unsigned int)s;
38 printf("3 + (-5) as unsigned = %u\n", dangerous);
39
40 return 0;
41}

warning

Mixing signed and unsigned types is a classic source of bugs. When a negative signed value is implicitly converted to unsigned, it wraps to a large positive number. Always ensure both operands have the same signedness.
Explicit Casting

Explicit casts tell the compiler to convert between types. They can silence warnings but do not make unsafe operations safe. Use them sparingly and only when the conversion is well-understood.

explicit_casting.c
C
1#include <stdio.h>
2#include <math.h>
3
4int main(void) {
5 /* Truncation: double to int */
6 double pi = 3.14159;
7 int truncated = (int)pi;
8 printf("(int)3.14159 = %d\n", truncated); /* 3 */
9
10 /* Rounding: use round() instead of casting */
11 int rounded = (int)round(pi);
12 printf("rounded = %d\n", rounded); /* 3 */
13
14 /* Sign change */
15 unsigned int big = 3000000000U;
16 int converted = (int)big; /* Implementation-defined behavior */
17 printf("(int)3000000000 = %d\n", converted);
18
19 /* Precision loss: double to float */
20 double precise = 1.0000001;
21 float less_precise = (float)precise;
22 printf("double: %.10f\n", precise);
23 printf("float: %.10f\n", less_precise);
24
25 /* Pointer casts: void* is implicitly convertible in C */
26 int x = 42;
27 void *vp = &x;
28 int *ip = (int *)vp; /* Cast not strictly needed in C */
29 int *ip2 = vp; /* Works in C (not C++) */
30 printf("value: %d\n", *ip2);
31
32 /* Casting between function pointers — undefined behavior */
33 void (*fp)(int) = (void (*)(int))printf;
34 /* fp(42); — don't do this */
35
36 return 0;
37}
The const Qualifier

const declares a variable as read-only. The compiler enforces that the variable is not modified after initialization. However, const does not make a value truly constant — it can be circumvented via pointers.

const_qualifier.c
C
1#include <stdio.h>
2
3int main(void) {
4 /* Basic const */
5 const int x = 10;
6 /* x = 20; — compile error */
7
8 /* Const pointer: pointer to const data */
9 const int *p1 = &x;
10 /* *p1 = 30; — can't modify data through p1 */
11 p1 = NULL; /* OK — can change the pointer itself */
12
13 /* Const data pointer: const pointer to mutable data */
14 int y = 20;
15 int *const p2 = &y;
16 *p2 = 30; /* OK — can modify data through p2 */
17 /* p2 = NULL; — can't change the pointer */
18
19 /* Both const pointer and const data */
20 const int *const p3 = &x;
21 /* *p3 = 40; — can't modify data */
22 /* p3 = NULL; — can't change pointer */
23
24 /* Array of const pointers */
25 const int *arr[3] = {&x, &y, NULL};
26
27 /* Casting away const — undefined behavior if you modify */
28 const int z = 42;
29 int *mutable = (int *)&z;
30 /* *mutable = 99; — UNDEFINED BEHAVIOR */
31
32 /* Const in function parameters */
33 printf("const param: %d\n", x);
34
35 return 0;
36}

best practice

Use const whenever a variable should not be modified. It communicates intent, enables compiler optimizations, and prevents accidental modification.
The volatile Qualifier

volatile tells the compiler that a variable may change at any time outside the program's control. Every read and write must occur exactly as written — no caching in registers, no reordering, no elision.

volatile_example.c
C
1#include <stdio.h>
2#include <signal.h>
3#include <stdatomic.h>
4
5/* Hardware register example (memory-mapped I/O) */
6#define STATUS_REG (*(volatile unsigned int *)0x40021000)
7
8void wait_for_ready(void) {
9 /* Compiler must re-read STATUS_REG each iteration */
10 while ((STATUS_REG & 0x01) == 0) {
11 /* Empty loop — volatile prevents optimization */ ;
12 }
13}
14
15/* Signal handler — shared variable must be volatile */
16volatile sig_atomic_t g_signal_received = 0;
17
18void signal_handler(int sig) {
19 g_signal_received = sig;
20}
21
22/* Counter that must not be optimized away */
23volatile int g_debug_counter = 0;
24
25int main(void) {
26 signal(SIGINT, signal_handler);
27
28 /* Without volatile, compiler might optimize this to infinite loop */
29 while (!g_signal_received) {
30 g_debug_counter++;
31 }
32
33 printf("Signal %d received\n", g_signal_received);
34 printf("Debug counter: %d\n", g_debug_counter);
35
36 return 0;
37}

warning

Do not use volatile for thread synchronization. Use _Atomic (C11) or mutexes instead. volatile does not provide atomicity or memory ordering guarantees between threads.
The restrict Qualifier (C99)

restrict is a promise to the compiler that the pointer is the sole means of accessing the data it points to during its lifetime. This allows aggressive optimization — the compiler can assume no aliasing.

restrict_example.c
C
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4
5/* Without restrict: compiler must assume src and dst may overlap */
6void copy_slow(char *dst, const char *src, size_t n) {
7 for (size_t i = 0; i < n; i++) {
8 dst[i] = src[i]; /* Compiler reads each byte individually */
9 }
10}
11
12/* With restrict: compiler knows dst and src don't alias */
13void copy_fast(char *restrict dst, const char *restrict src, size_t n) {
14 /* Compiler can use memcpy-style optimizations */
15 memcpy(dst, src, n);
16}
17
18/* Real-world: vector addition */
19void vec_add(float *restrict result,
20 const float *restrict a,
21 const float *restrict b,
22 size_t n) {
23 for (size_t i = 0; i < n; i++) {
24 result[i] = a[i] + b[i];
25 }
26}
27
28/* WRONG: using restrict when pointers alias */
29void bad_example(float *restrict x, float *restrict y) {
30 *x = 1.0f;
31 *y = 2.0f;
32 /* If x == y, result is undefined — restrict was violated */
33 printf("%f\n", *x); /* Could print 1.0 or 2.0 */
34}
35
36int main(void) {
37 float a[4] = {1.0f, 2.0f, 3.0f, 4.0f};
38 float b[4] = {5.0f, 6.0f, 7.0f, 8.0f};
39 float r[4];
40
41 vec_add(r, a, b, 4);
42 for (int i = 0; i < 4; i++) {
43 printf("%.1f ", r[i]);
44 }
45 printf("\n");
46
47 return 0;
48}
Alignment (C11)

C11 added _Alignas and _Alignof for explicit control over data alignment. This is critical for SIMD operations, cache-line alignment, and memory-mapped hardware access.

alignment_example.c
C
1#include <stdio.h>
2#include <stdalign.h>
3
4/* Cache-line aligned structure (typically 64 bytes) */
5struct alignas(64) CacheLine {
6 int data[16];
7 char padding[64 - sizeof(int) * 16];
8};
9
10/* Aligned to 32-byte boundary for SIMD */
11alignas(32) float simd_array[8] = {1.0f, 2.0f, 3.0f, 4.0f,
12 5.0f, 6.0f, 7.0f, 8.0f};
13
14int main(void) {
15 printf("int alignment: %zu\n", _Alignof(int));
16 printf("double alignment: %zu\n", _Alignof(double));
17 printf("CacheLine size: %zu\n", sizeof(struct CacheLine));
18 printf("CacheLine align: %zu\n", _Alignof(struct CacheLine));
19 printf("simd_array addr: %p\n", (void *)simd_array);
20 printf("simd_array align: %zu\n", _Alignof(float[8]));
21
22 /* Dynamically aligned allocation */
23 void *p = NULL;
24 int err = aligned_alloc(64, 256, &p);
25 if (err == 0 && p) {
26 printf("Aligned block: %p\n", p);
27 free(p);
28 }
29
30 return 0;
31}
_Atomic Types (C11)

C11 introduced _Atomic for thread-safe access to shared data without locks. The <stdatomic.h> header provides convenience macros and operations.

atomic_example.c
C
1#include <stdio.h>
2#include <stdatomic.h>
3
4/* Atomic counter — safe to modify from multiple threads */
5static atomic_int g_counter = ATOMIC_VAR_INIT(0);
6
7/* Atomic flag — guaranteed lock-free */
8static atomic_flag g_flag = ATOMIC_FLAG_INIT;
9
10int main(void) {
11 /* Store and load */
12 atomic_store(&g_counter, 42);
13 int val = atomic_load(&g_counter);
14 printf("Value: %d\n", val);
15
16 /* Fetch-and-add */
17 int old = atomic_fetch_add(&g_counter, 1);
18 printf("Old: %d, New: %d\n", old, atomic_load(&g_counter));
19
20 /* Compare-and-swap */
21 int expected = 43;
22 int desired = 99;
23 bool success = atomic_compare_exchange_weak(&g_counter,
24 &expected, desired);
25 printf("CAS %s: expected=%d, current=%d\n",
26 success ? "succeeded" : "failed",
27 expected, atomic_load(&g_counter));
28
29 /* Atomic flag: test and set */
30 if (!atomic_flag_test_and_set(&g_flag)) {
31 printf("Flag acquired\n");
32 } else {
33 printf("Flag already set\n");
34 }
35
36 atomic_flag_clear(&g_flag);
37
38 return 0;
39}
Type Punning

Type punning is accessing the same memory as a different type. Union-based punning is explicitly allowed in C. Pointer-based punning violates the strict aliasing rule and can cause undefined behavior.

type_punning.c
C
1#include <stdio.h>
2#include <string.h>
3
4/* Union-based type punning (ALLOWED in C) */
5union FloatInt {
6 float f;
7 unsigned int u;
8};
9
10void print_float_bits(float f) {
11 union FloatInt fi = {.f = f};
12 printf("float %f as hex: 0x%08X\n", f, fi.u);
13
14 /* Extract sign, exponent, mantissa */
15 unsigned int sign = (fi.u >> 31) & 1;
16 unsigned int exponent = (fi.u >> 23) & 0xFF;
17 unsigned int mantissa = fi.u & 0x7FFFFF;
18 printf(" sign=%u exponent=%u mantissa=0x%06X\n",
19 sign, exponent, mantissa);
20}
21
22/* Pointer-based type punning — VIOLATES strict aliasing */
23void dangerous_punning(void) {
24 int i = 0x3F800000; /* IEEE 754 for 1.0f */
25 /* float *fp = (float *)&i; — undefined behavior! */
26 /* Use memcpy instead (compiler optimizes it away) */
27 float f;
28 memcpy(&f, &i, sizeof(f));
29 printf("memcpy pun: %f\n", f);
30}
31
32/* Safe pattern: memcpy for type punning */
33float int_to_float(unsigned int bits) {
34 float f;
35 memcpy(&f, &bits, sizeof(f));
36 return f;
37}
38
39int main(void) {
40 print_float_bits(3.14f);
41 print_float_bits(-0.0f);
42 print_float_bits(1.0f);
43
44 dangerous_punning();
45 printf("safe pun: %f\n", int_to_float(0x3F800000));
46
47 return 0;
48}
Strict Aliasing Rule

The strict aliasing rule says that an object shall only be accessed by an lvalue of a compatible type, a qualified version thereof, or a character type. Violating this rule is undefined behavior and can cause miscompilation with optimizations enabled.

strict_aliasing.c
C
1#include <stdio.h>
2#include <string.h>
3
4/* VIOLATION: accessing int as float through pointer */
5int bad_example(int *ip) {
6 float *fp = (float *)ip; /* Strict aliasing violation! */
7 return (int)(*fp);
8}
9
10/* COMPLIANT: use union for type punning */
11union IntFloat {
12 int i;
13 float f;
14};
15
16int safe_example(int *ip) {
17 union IntFloat u;
18 u.i = *ip;
19 return (int)u.f;
20}
21
22/* COMPLIANT: use memcpy for type punning */
23int memcpy_example(int *ip) {
24 float f;
25 memcpy(&f, ip, sizeof(f));
26 return (int)f;
27}
28
29/* Character type exception: char* can alias anything */
30void print_bytes(const void *ptr, size_t size) {
31 const unsigned char *bp = (const unsigned char *)ptr;
32 for (size_t i = 0; i < size; i++) {
33 printf("%02X ", bp[i]);
34 }
35 printf("\n");
36}
37
38int main(void) {
39 int i = 0x3F800000;
40 printf("union pun: %f\n", (float)i);
41 printf("memcpy pun: %f\n",
42 *(float *)(void *)&i); /* Still risky via pointer cast */
43
44 print_bytes(&i, sizeof(i));
45
46 return 0;
47}

info

Compile with -fno-strict-aliasing to disable strict aliasing optimizations if your code relies on type punning via pointers. Or better: use unions or memcpy.
Type-Generic Expressions: _Generic (C11)

_Generic selects an expression based on the type of a controlling expression — similar to pattern matching on types. It is evaluated at compile time with zero runtime cost.

generic_example.c
C
1#include <stdio.h>
2#include <math.h>
3
4/* Type-safe print macro using _Generic */
5#define print_val(x) _Generic((x), \
6 int: printf("%d\n", x), \
7 long: printf("%ld\n", x), \
8 double: printf("%f\n", x), \
9 float: printf("%f\n", (double)x), \
10 char *: printf("%s\n", x), \
11 default: printf("unknown type\n") \
12)
13
14/* Type-safe absolute value */
15#define safe_abs(x) _Generic((x), \
16 int: abs, \
17 long: labs, \
18 double: fabs, \
19 float: fabsf \
20)(x)
21
22/* Type-safe minimum */
23#define xmin(a, b) _Generic((a), \
24 int: ((a) < (b) ? (a) : (b)), \
25 double: fmin((double)(a), (double)(b)), \
26 float: fminf((float)(a), (float)(b)) \
27)
28
29int main(void) {
30 print_val(42);
31 print_val(3.14);
32 print_val("hello");
33
34 printf("abs(-5) = %d\n", safe_abs(-5));
35 printf("fabs(-3.14) = %f\n", safe_abs(-3.14));
36
37 int x = 10, y = 20;
38 printf("min(%d, %d) = %d\n", x, y, xmin(x, y));
39
40 return 0;
41}
Common Type-Related Bugs

Type-related bugs are among the most insidious in C. They often compile without warnings, produce correct results on one platform, and silently fail on another.

BugDescriptionFix
Signed overflowUndefined behavior, compiler may optimize awayCheck before arithmetic
Implicit sign conversionNegative signed becomes huge unsignedMatch signedness of operands
Narrowing conversionint to char truncates high bitsCheck range before converting
Printf format mismatchWrong format specifier for typeUse PRI/SCN macros from inttypes.h
Strict aliasing violationAccessing memory through wrong pointer typeUse union or memcpy
Array decaysizeof(array) in function returns pointer sizePass length separately
common_bugs.c
C
1#include <stdio.h>
2#include <string.h>
3#include <limits.h>
4
5/* Bug 1: size_t arithmetic underflow */
6int sum_array_bad(int *arr, size_t len) {
7 int total = 0;
8 for (size_t i = 0; i < len; i++) {
9 total += arr[i];
10 }
11 return total;
12}
13
14/* Bug 2: printf format mismatch */
15void bug_printf(void) {
16 long long big = 9000000000LL;
17 /* printf("%d", big); — wrong format, undefined behavior */
18 printf("%lld\n", big); /* correct */
19}
20
21/* Bug 3: char sign is platform-dependent */
22void bug_char_sign(void) {
23 char c = 0xFF;
24 /* On platforms where char is unsigned: c == 255 */
25 /* On platforms where char is signed: c == -1 */
26 unsigned char uc = (unsigned char)c;
27 printf("char value: %d, unsigned: %u\n", c, uc);
28}
29
30/* Bug 4: sizeof loses information in function parameters */
31void bug_sizeof_array(int arr[]) {
32 /* sizeof(arr) is sizeof(int*), NOT sizeof the array */
33 printf("arr size in func: %zu\n", sizeof(arr));
34}
35
36int main(void) {
37 int data[] = {1, 2, 3, 4, 5};
38 bug_sizeof_array(data);
39 printf("arr size in main: %zu\n", sizeof(data));
40 return 0;
41}

best practice

Compile with -Wall -Wextra -Wconversion -Wsign-conversion to catch most type-related issues at compile time. Treat warnings as errors.
$Blueprint — Engineering Documentation·Section ID: C-TYPE-SYSTEM·Revision: 1.0